diff --git a/VERSION.txt b/VERSION.txt
index c41a182d65f..d68345e6a0d 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1,4 +1,89 @@
-jetty-9.3.4-SNAPSHOT
+jetty-9.4.0-SNAPSHOT
+
+jetty-9.3.6.v20151106 - 06 November 2015
+ + 419966 Add ContentProvider that submits multipart/form-data.
+ + 472675 No main manifest attribute, in jetty-runner regression
+ + 476641 Proxy rewriteTarget() null return does not call error handler.
+ + 478757 DebugHandler thread name is mangled
+ + 479179 Fixed NPE from debug
+ + 479378 Incorrect REQUEST_URI.
+ + 479712 Documented --approve-all-licenses
+ + 479832 Use system properties for gcloud config for GCloudDatastore session
+ manager
+ + 479839 Regression when starting application with excessive scan times
+ + 479865 IllegalStateException: Multiple servlets map to path: *.jsp: jsp,jsp
+ + 480061 HTTP/2 server doesn't send GOAWAY frame when shutting down.
+ + 480162 Continuations behavior differences due to HttpURI behavior
+ + 480260 HPack decode error for buffers with offset.
+ + 480272 Update to newer jdt ecj version
+ + 480452 Large downloads via FastCGI proxy keep HttpClient connections active.
+ + 480764 Error parsing empty multipart.
+ + 481006 SSL requests intermittently fail with EOFException when SSL
+ renegotiation is disallowed.
+ + 481203 Add ability to set configurations to apply to WebAppContext for
+ jetty-maven-plugin
+ + 481225 Secondary resources with query parameters are not properly pushed.
+ + 481236 Make ShutdownMonitor java security manager friendly
+ + 481355 Nested Symlinks
+ + 481373 Corner cases where session may remain in JDBCSessionManager memory
+ + 481385 Incorrect parsing of END_REQUEST frames.
+ + 481418 ResourceHandler sets last modified
+ + 481437 Port ConnectHandler connect and context functionality from Jetty 8.
+ + 481554 DispatcherType reset race
+
+jetty-9.3.5.v20151012 - 12 October 2015
+ + 479343 calls to MetaData#orderFragments() with relative ordering adds
+ duplicate jars
+ + 479537 Server preface sent after client preface reply.
+ + 479584 WS Session does not contain UpgradeRequest information in
+ WebSocketAdapter.onWebSocketConnect callback
+
+jetty-9.3.4.v20151007 - 07 October 2015
+ + 428474 Expose batch mode in the Jetty WebSocket API
+ + 472082 isOpen returns true on CLOSING Connection
+ + 474936 WebSocketSessions are not always cleaned out from openSessions
+ + 475209 WebSocketServerFactory should not hand null object to
+ DecoratedObjectFactory
+ + 476023 Incorrect trimming of WebSocket close reason
+ + 476049 When using WebSocket Session.close() there should be no status code
+ or reason sent
+ + 476170 Support servers that close connections without sending Connection:
+ close header.
+ + 476720 getTrustStoreResource fixed
+ + 477087 Enforce that the preface contains a SETTINGS frame.
+ + 477123 AsyncListener callbacks need context scope
+ + 477270 Add ability to send a single PRIORITY frame.
+ + 477278 Refactored DefaultServlet for cached Gzip & Etags
+ + 477385 Make jetty osgi manifests only resolve jetty packages against a
+ single distro version
+ + 477641 ALPN classes exposed to webapps - fixed typo
+ + 477680 Encode merged query parameters
+ + 477737 Improve handling of etags with dynamic and static gzip
+ + 477757 Null args in TypeUtil .call & .construct result in confusing
+ exceptions
+ + 477817 Fixed memory leak in QueuedThreadPool
+ + 477878 HttpClient over HTTP/2 doesn't close upload stream.
+ + 477885 Jetty HTTP2 client fails to connect with Netty server - HTTP2 client
+ preface missing or corrupt.
+ + 477890 Overwhelmed HTTP/2 server discards data.
+ + 477895 Prevent leak of handles to deleted files after redeploy
+ + 477900 Increase client authentication default max content size
+ + 478008 Do not reset current value of CounterStatistics
+ + 478021 Client sending Connection: close does not shutdown output.
+ + 478105 prependFilterMapping check for null FilterHolder
+ + 478239 Remove pointless synchronize in infinispan scavenging
+ + 478247 WebappClassLoader pinned after redeploy
+ + 478275 Priority information in HEADERS frame is not sent.
+ + 478280 property file in temp directory
+ + 478372 JavaUtilLog setSourceClass and setSourceMethod
+ + 478434 Priority weights should be between 1 and 256 inclusive.
+ + 478752 Clarify support for HttpServletRequest.upgrade()
+ + 478757 DebugHandler thread name is mangled
+ + 478829 WebsocketSession not cleaned up / memory leak
+ + 478862 Update to jstl 1.2.5
+ + 478923 threads stuck at SharedBlockingCallback$Blocker.block
+ + 479026 Wrong CONNECT request idle timeout.
+ + 479277 HttpClient with HTTP/2 transport does not work for "https" URLs.
jetty-9.3.3.v20150827 - 27 August 2015
+ 470311 Introduce a proxy-protocol module.
diff --git a/aggregates/jetty-all-compact3/pom.xml b/aggregates/jetty-all-compact3/pom.xml
index 0e2c674eb99..c323c8bdcd4 100644
--- a/aggregates/jetty-all-compact3/pom.xml
+++ b/aggregates/jetty-all-compact3/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.1-SNAPSHOT
+ 9.4.0-SNAPSHOT../../pom.xml4.0.0
diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml
index 4ce3ad4bf8b..3e0e91ef960 100644
--- a/aggregates/jetty-all/pom.xml
+++ b/aggregates/jetty-all/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT../../pom.xml4.0.0
diff --git a/aggregates/jetty-websocket-all/pom.xml b/aggregates/jetty-websocket-all/pom.xml
index 3c9c2689b83..ee65e2390cf 100644
--- a/aggregates/jetty-websocket-all/pom.xml
+++ b/aggregates/jetty-websocket-all/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.1.0-SNAPSHOT
+ 9.1.3-SNAPSHOT../../pom.xml4.0.0
@@ -24,7 +24,7 @@
**/MANIFEST.MF
- org.slf4j,org.eclipse.jetty.orbit,org.mortbay.jetty.alpn
+ javax.annotations,org.objectweb.asm,javax.servlet,org.slf4j,org.eclipse.jetty.orbit,org.mortbay.jetty.npn${project.build.directory}/classesfalsetrue
diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml
index 7267c80a59a..66ecfd4fcff 100644
--- a/apache-jsp/pom.xml
+++ b/apache-jsp/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0apache-jsp
@@ -88,8 +88,8 @@
- org.eclipse.jetty.orbit
- org.eclipse.jdt.core
+ org.eclipse.jdt.core.compiler
+ ecj
diff --git a/apache-jsp/src/main/config/modules/apache-jsp.mod b/apache-jsp/src/main/config/modules/apache-jsp.mod
index 5123670cb0c..c816f61c049 100644
--- a/apache-jsp/src/main/config/modules/apache-jsp.mod
+++ b/apache-jsp/src/main/config/modules/apache-jsp.mod
@@ -1,6 +1,5 @@
-#
-# Apache JSP Module
-#
+[description]
+Enables use of the apache implementation of JSP
[name]
apache-jsp
diff --git a/apache-jstl/pom.xml b/apache-jstl/pom.xml
index 4d21ada6b06..3396c849aa8 100644
--- a/apache-jstl/pom.xml
+++ b/apache-jstl/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0apache-jstl
diff --git a/apache-jstl/src/main/config/modules/apache-jstl.mod b/apache-jstl/src/main/config/modules/apache-jstl.mod
index e4a9001a2ae..d7c703e7ea9 100644
--- a/apache-jstl/src/main/config/modules/apache-jstl.mod
+++ b/apache-jstl/src/main/config/modules/apache-jstl.mod
@@ -1,6 +1,5 @@
-#
-# Apache JSTL
-#
+[description]
+Enables the apache version of JSTL
[name]
apache-jstl
diff --git a/examples/async-rest/async-rest-jar/pom.xml b/examples/async-rest/async-rest-jar/pom.xml
index 447d2c1d16d..1d3dc196926 100644
--- a/examples/async-rest/async-rest-jar/pom.xml
+++ b/examples/async-rest/async-rest-jar/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyexample-async-rest
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0org.eclipse.jetty.example-async-rest
diff --git a/examples/async-rest/async-rest-webapp/pom.xml b/examples/async-rest/async-rest-webapp/pom.xml
index a4b5301b361..52b08a7784a 100644
--- a/examples/async-rest/async-rest-webapp/pom.xml
+++ b/examples/async-rest/async-rest-webapp/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyexample-async-rest
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0org.eclipse.jetty.example-async-rest
diff --git a/examples/async-rest/pom.xml b/examples/async-rest/pom.xml
index 46ac32d2483..3a3f023f0dd 100644
--- a/examples/async-rest/pom.xml
+++ b/examples/async-rest/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jetty.examplesexamples-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT../pom.xml4.0.0
diff --git a/examples/embedded/pom.xml b/examples/embedded/pom.xml
index 0ec86392708..93806b92a42 100644
--- a/examples/embedded/pom.xml
+++ b/examples/embedded/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jetty.examplesexamples-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT../pom.xml4.0.0
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 8d984bf2d1a..8e19c0a20ee 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
@@ -55,6 +55,7 @@ import org.eclipse.jetty.server.SslConnectionFactory;
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;
@@ -74,7 +75,8 @@ public class Http2Server
ServletContextHandler context = new ServletContextHandler(server, "/",ServletContextHandler.SESSIONS);
context.setResourceBase("src/main/resources/docroot");
- context.addFilter(PushSessionCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
+ context.addFilter(PushCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
+ // context.addFilter(PushSessionCacheFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
context.addFilter(PushedTilesFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
context.addServlet(new ServletHolder(servlet), "/test/*");
context.addServlet(DefaultServlet.class, "/").setInitParameter("maxCacheSize","81920");
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
index 9cfc2ddcb77..38534445fb8 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
@@ -24,10 +24,12 @@ import java.lang.management.ManagementFactory;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
+import org.eclipse.jetty.deploy.bindings.DebugListenerBinding;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -155,6 +157,9 @@ public class LikeJettyXml
// === jetty-deploy.xml ===
DeploymentManager deployer = new DeploymentManager();
+ DebugListener debug = new DebugListener(System.out,true,true,true);
+ server.addBean(debug);
+ deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
deployer.setContexts(contexts);
deployer.setContextAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
index e6882556ad3..312d0adc7f3 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebApp.java
@@ -52,9 +52,8 @@ public class OneWebApp
WebAppContext webapp = new WebAppContext();
webapp.setContextPath("/");
File warFile = new File(
- "../../jetty-distribution/target/distribution/test/webapps/test/");
+ "../../tests/test-jmx/jmx-webapp/target/jmx-webapp");
webapp.setWar(warFile.getAbsolutePath());
- webapp.addAliasCheck(new AllowSymLinkAliasChecker());
// A WebAppContext is a ContextHandler as well so it needs to be set to
// the server so it is aware of where to send the appropriate requests.
diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java
index f391be7e021..58c177a59fa 100644
--- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java
+++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneWebAppWithJsp.java
@@ -62,6 +62,7 @@ public class OneWebAppWithJsp
+ warFile.getAbsolutePath() );
}
webapp.setWar( warFile.getAbsolutePath() );
+ webapp.setExtractWAR(true);
// This webapp will use jsps and jstl. We need to enable the
// AnnotationConfiguration in order to correctly
@@ -100,6 +101,8 @@ public class OneWebAppWithJsp
// Start things up!
server.start();
+
+ server.dumpStdErr();
// The use of server.join() the will make the current thread join and
// wait until the server is done executing.
diff --git a/examples/embedded/src/main/resources/java-util-logging.properties b/examples/embedded/src/main/resources/java-util-logging.properties
new file mode 100644
index 00000000000..4aaa236d96c
--- /dev/null
+++ b/examples/embedded/src/main/resources/java-util-logging.properties
@@ -0,0 +1,9 @@
+
+# Logging
+handlers = java.util.logging.ConsoleHandler
+.level = INFO
+
+java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$-6s %2$s %5$s%6$s%n
+
+# Console Logging
+java.util.logging.ConsoleHandler.level = ALL
\ No newline at end of file
diff --git a/examples/embedded/src/main/resources/jetty-logging.properties b/examples/embedded/src/main/resources/jetty-logging.properties
index 04163c07af7..62624e2d4a4 100644
--- a/examples/embedded/src/main/resources/jetty-logging.properties
+++ b/examples/embedded/src/main/resources/jetty-logging.properties
@@ -1,9 +1,9 @@
-org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+#org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog
+#org.eclipse.jetty.util.log.javautil.PROPERTIES=java-util-logging.properties
+#org.eclipse.jetty.util.log.SOURCE=true
org.eclipse.jetty.LEVEL=INFO
org.eclipse.jetty.STACKS=true
-org.eclipse.jetty.SOURCE=false
#org.eclipse.jetty.STACKS=false
-#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.io.LEVEL=DEBUG
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
diff --git a/examples/pom.xml b/examples/pom.xml
index 69bc2c27f9a..33883db3bc4 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -21,7 +21,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT../pom.xmlorg.eclipse.jetty.examples
diff --git a/jetty-alpn/jetty-alpn-client/pom.xml b/jetty-alpn/jetty-alpn-client/pom.xml
index e25c2800817..5128921e97f 100644
--- a/jetty-alpn/jetty-alpn-client/pom.xml
+++ b/jetty-alpn/jetty-alpn-client/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-alpn-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-alpn-client
diff --git a/jetty-alpn/jetty-alpn-server/pom.xml b/jetty-alpn/jetty-alpn-server/pom.xml
index 3465055281a..98fc5250fdd 100644
--- a/jetty-alpn/jetty-alpn-server/pom.xml
+++ b/jetty-alpn/jetty-alpn-server/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-alpn-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-alpn-server
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_60.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_60.mod
new file mode 100644
index 00000000000..9d207d9a65c
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_60.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.5.v20150921/alpn-boot-8.1.5.v20150921.jar|lib/alpn/alpn-boot-8.1.5.v20150921.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.5.v20150921.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_65.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_65.mod
new file mode 100644
index 00000000000..03b32d0774d
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_65.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.6.v20151105/alpn-boot-8.1.6.v20151105.jar|lib/alpn/alpn-boot-8.1.6.v20151105.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.6.v20151105.jar
diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_66.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_66.mod
new file mode 100644
index 00000000000..03b32d0774d
--- /dev/null
+++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/alpn-impl/alpn-1.8.0_66.mod
@@ -0,0 +1,8 @@
+[name]
+protonego-boot
+
+[files]
+http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.1.6.v20151105/alpn-boot-8.1.6.v20151105.jar|lib/alpn/alpn-boot-8.1.6.v20151105.jar
+
+[exec]
+-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.6.v20151105.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 7928e649280..10997501ff6 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,11 +1,10 @@
-# ALPN is provided via a -Xbootclasspath that modifies the secure connections
-# in java to support the ALPN layer needed for HTTP/2.
-#
-# 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.
+[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
diff --git a/jetty-alpn/pom.xml b/jetty-alpn/pom.xml
index 1c9efdddf68..4157d6c746c 100644
--- a/jetty-alpn/pom.xml
+++ b/jetty-alpn/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-alpn-parent
diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml
index 6690bce63ea..8f432097d11 100644
--- a/jetty-annotations/pom.xml
+++ b/jetty-annotations/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-annotations
@@ -20,7 +20,6 @@
true
- javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=5,*osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"
diff --git a/jetty-annotations/src/main/config/modules/annotations.mod b/jetty-annotations/src/main/config/modules/annotations.mod
index 65e4654127d..4217be54fb3 100644
--- a/jetty-annotations/src/main/config/modules/annotations.mod
+++ b/jetty-annotations/src/main/config/modules/annotations.mod
@@ -1,15 +1,11 @@
-#
-# Jetty Annotation Scanning Module
-#
+[description]
+Enables Annotation scanning for deployed webapplications.
[depend]
-# Annotations needs plus, and jndi features
plus
[lib]
-# Annotations needs jetty annotation jars
lib/jetty-annotations-${jetty.version}.jar
-# Need annotation processing jars too
lib/annotations/*.jar
[xml]
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
index 8713d2bbc3d..48d5b93f2ea 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
@@ -88,7 +88,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
protected CounterStatistic _webInfLibStats;
protected CounterStatistic _webInfClassesStats;
protected Pattern _sciExcludePattern;
-
+ protected ServiceLoader _loadedInitializers = null;
/**
* TimeStatistic
*
@@ -413,6 +413,9 @@ public class AnnotationConfiguration extends AbstractConfiguration
context.removeBean(starter);
context.removeAttribute(CONTAINER_INITIALIZER_STARTER);
}
+
+ if (_loadedInitializers != null)
+ _loadedInitializers.reload();
}
/**
@@ -823,22 +826,28 @@ public class AnnotationConfiguration extends AbstractConfiguration
return sci.getClass().getClassLoader()==context.getClassLoader().getParent();
}
+ /**
+ * Get SCIs that are not excluded from consideration
+ * @param context the web app context
+ * @return the list of non-excluded servlet container initializers
+ * @throws Exception if unable to get list
+ */
public List getNonExcludedInitializers (WebAppContext context)
throws Exception
{
ArrayList nonExcludedInitializers = new ArrayList();
-
+
//We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
long start = 0;
ClassLoader old = Thread.currentThread().getContextClassLoader();
- ServiceLoader loadedInitializers = null;
+
try
{
if (LOG.isDebugEnabled())
start = System.nanoTime();
Thread.currentThread().setContextClassLoader(context.getClassLoader());
- loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
+ _loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
}
finally
{
@@ -847,21 +856,22 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled())
LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
-
+
Map sciResourceMap = new HashMap();
ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
//Get initial set of SCIs that aren't from excluded jars or excluded by the containerExclusionPattern, or excluded
//because containerInitializerOrdering omits it
- for (ServletContainerInitializer sci:loadedInitializers)
- {
+ for (ServletContainerInitializer sci:_loadedInitializers)
+ {
+
if (matchesExclusionPattern(sci))
{
if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
continue;
}
-
+
Resource sciResource = getJarFor(sci);
if (isFromExcludedJar(context, sci, sciResource))
{
@@ -921,7 +931,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
for (Map.Entry entry:sciResourceMap.entrySet())
{
- if (webInfJar.equals(entry.getValue()))
+ if (webInfJar.equals(entry.getValue()))
nonExcludedInitializers.add(entry.getKey());
}
}
@@ -933,7 +943,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
int i=0;
for (ServletContainerInitializer sci:nonExcludedInitializers)
LOG.debug("ServletContainerInitializer: {} {} from {}",(++i), sci.getClass().getName(), sciResourceMap.get(sci));
- }
+ }
+
return nonExcludedInitializers;
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
index 587743cc341..60e42d8c8e0 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
@@ -558,11 +558,14 @@ public class AnnotationParser
if (!isParsed(className) || resolver.shouldOverride(className))
{
className = className.replace('.', '/')+".class";
- URL resource = Loader.getResource(this.getClass(), className);
+ URL resource = Loader.getResource(className);
if (resource!= null)
{
Resource r = Resource.newResource(resource);
- scanClass(handlers, null, r.getInputStream());
+ try (InputStream is = r.getInputStream())
+ {
+ scanClass(handlers, null, is);
+ }
}
}
}
@@ -590,11 +593,14 @@ public class AnnotationParser
if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
{
String nameAsResource = cz.getName().replace('.', '/')+".class";
- URL resource = Loader.getResource(this.getClass(), nameAsResource);
+ URL resource = Loader.getResource(nameAsResource);
if (resource!= null)
{
Resource r = Resource.newResource(resource);
- scanClass(handlers, null, r.getInputStream());
+ try (InputStream is = r.getInputStream())
+ {
+ scanClass(handlers, null, is);
+ }
}
}
}
@@ -646,11 +652,14 @@ public class AnnotationParser
if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s))))
{
s = s.replace('.', '/')+".class";
- URL resource = Loader.getResource(this.getClass(), s);
+ URL resource = Loader.getResource(s);
if (resource!= null)
{
Resource r = Resource.newResource(resource);
- scanClass(handlers, null, r.getInputStream());
+ try (InputStream is = r.getInputStream())
+ {
+ scanClass(handlers, null, is);
+ }
}
}
}
@@ -845,8 +854,11 @@ public class AnnotationParser
if (fullname.endsWith(".class"))
{
- scanClass(handlers, null, r.getInputStream());
- return;
+ try (InputStream is=r.getInputStream())
+ {
+ scanClass(handlers, null, is);
+ return;
+ }
}
if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r);
@@ -963,11 +975,14 @@ public class AnnotationParser
if ((resolver == null)
||
- (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
+ (!resolver.isExcluded(shortName) && (!isParsed(shortName) || resolver.shouldOverride(shortName))))
{
Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
- scanClass(handlers, jar, clazz.getInputStream());
+ try (InputStream is = clazz.getInputStream())
+ {
+ scanClass(handlers, jar, is);
+ }
}
}
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
index 6faa9590e34..7cb4dfd861f 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/Util.java
@@ -201,7 +201,7 @@ public class Util
}
case Type.OBJECT:
{
- return (Loader.loadClass(null, t.getClassName()));
+ return (Loader.loadClass(t.getClassName()));
}
case Type.SHORT:
{
diff --git a/jetty-ant/pom.xml b/jetty-ant/pom.xml
index f1c72ce1b93..3d97aac00ff 100644
--- a/jetty-ant/pom.xml
+++ b/jetty-ant/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-ant
diff --git a/jetty-cdi/cdi-core/pom.xml b/jetty-cdi/cdi-core/pom.xml
index cfcd1baea91..ea315171831 100644
--- a/jetty-cdi/cdi-core/pom.xml
+++ b/jetty-cdi/cdi-core/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jetty.cdijetty-cdi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0cdi-core
diff --git a/jetty-cdi/cdi-full-servlet/pom.xml b/jetty-cdi/cdi-full-servlet/pom.xml
index c02720ffba3..f8509144daf 100644
--- a/jetty-cdi/cdi-full-servlet/pom.xml
+++ b/jetty-cdi/cdi-full-servlet/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jetty.cdijetty-cdi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0cdi-full-servlet
diff --git a/jetty-cdi/cdi-servlet/pom.xml b/jetty-cdi/cdi-servlet/pom.xml
index e1265a295a4..e3b05934900 100644
--- a/jetty-cdi/cdi-servlet/pom.xml
+++ b/jetty-cdi/cdi-servlet/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jetty.cdijetty-cdi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0cdi-servlet
diff --git a/jetty-cdi/cdi-servlet/src/main/config/modules/cdi.mod b/jetty-cdi/cdi-servlet/src/main/config/modules/cdi.mod
index ebffb55aed9..68a926d62f2 100644
--- a/jetty-cdi/cdi-servlet/src/main/config/modules/cdi.mod
+++ b/jetty-cdi/cdi-servlet/src/main/config/modules/cdi.mod
@@ -1,6 +1,5 @@
-#
-# [EXPERIMENTAL] CDI / Weld Jetty module
-#
+[description]
+Experimental CDI/Weld integration
[depend]
deploy
diff --git a/jetty-cdi/cdi-websocket/pom.xml b/jetty-cdi/cdi-websocket/pom.xml
index 27e5eff8c43..07c6a5a7c35 100644
--- a/jetty-cdi/cdi-websocket/pom.xml
+++ b/jetty-cdi/cdi-websocket/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jetty.cdijetty-cdi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0cdi-websocket
diff --git a/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/basicscope/ScopeBasicsTest.java b/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/basicscope/ScopeBasicsTest.java
index 3561b2499bd..1b5892086fd 100644
--- a/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/basicscope/ScopeBasicsTest.java
+++ b/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/basicscope/ScopeBasicsTest.java
@@ -57,6 +57,7 @@ public class ScopeBasicsTest
/**
* Validation of Scope / Inject logic on non-websocket-scoped classes
+ * @throws Exception on test failure
*/
@Test
public void testBasicBehavior() throws Exception
diff --git a/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/wsscope/WebSocketScopeBaselineTest.java b/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/wsscope/WebSocketScopeBaselineTest.java
index 3c9e2e135d4..ba1fd997e51 100644
--- a/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/wsscope/WebSocketScopeBaselineTest.java
+++ b/jetty-cdi/cdi-websocket/src/test/java/org/eclipse/jetty/cdi/websocket/wsscope/WebSocketScopeBaselineTest.java
@@ -61,6 +61,7 @@ public class WebSocketScopeBaselineTest
* Test behavior of {@link WebSocketScope} in basic operation.
*
* Food is declared as part of WebSocketScope, and as such, only 1 instance of it can exist.
+ * @throws Exception on test failure
*/
@Test
public void testScopeBehavior() throws Exception
diff --git a/jetty-cdi/pom.xml b/jetty-cdi/pom.xml
index 279de82909d..b1cd311412e 100644
--- a/jetty-cdi/pom.xml
+++ b/jetty-cdi/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0org.eclipse.jetty.cdi
diff --git a/jetty-cdi/test-cdi-webapp/pom.xml b/jetty-cdi/test-cdi-webapp/pom.xml
index 44100430568..931bfc9fc9c 100644
--- a/jetty-cdi/test-cdi-webapp/pom.xml
+++ b/jetty-cdi/test-cdi-webapp/pom.xml
@@ -20,7 +20,7 @@
org.eclipse.jetty.cdijetty-cdi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0test-cdi-webapp
diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml
index cc2168807f2..9450cdcc994 100644
--- a/jetty-client/pom.xml
+++ b/jetty-client/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0
@@ -15,23 +15,6 @@
-
- org.apache.felix
- maven-bundle-plugin
- true
-
-
-
- manifest
-
-
-
- javax.net.*,*
-
-
-
-
- org.codehaus.mojofindbugs-maven-plugin
@@ -65,6 +48,44 @@
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4.2
+
+
+ package
+
+ shade
+
+
+ true
+ hybrid
+
+
+ org.eclipse.jetty:jetty-http
+ org.eclipse.jetty:jetty-io
+ org.eclipse.jetty:jetty-util
+
+
+
+
+ org.eclipse.jetty.http
+ org.eclipse.jetty.client.shaded.http
+
+
+ org.eclipse.jetty.io
+ org.eclipse.jetty.client.shaded.io
+
+
+ org.eclipse.jetty.util
+ org.eclipse.jetty.client.shaded.util
+
+
+
+
+
+
diff --git a/jetty-client/src/main/config/modules/client.mod b/jetty-client/src/main/config/modules/client.mod
index 39b58d4e691..e9d13c8c689 100644
--- a/jetty-client/src/main/config/modules/client.mod
+++ b/jetty-client/src/main/config/modules/client.mod
@@ -1,6 +1,5 @@
-#
-# Client Feature
-#
+[description]
+Adds the Jetty HTTP client to the server classpath.
[lib]
lib/jetty-client-${jetty.version}.jar
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java
new file mode 100644
index 00000000000..d3b13b668d1
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java
@@ -0,0 +1,199 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractConnectionPool implements ConnectionPool, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(AbstractConnectionPool.class);
+
+ private final AtomicBoolean closed = new AtomicBoolean();
+ private final AtomicInteger connectionCount = new AtomicInteger();
+ private final Destination destination;
+ private final int maxConnections;
+ private final Callback requester;
+
+ protected AbstractConnectionPool(Destination destination, int maxConnections, Callback requester)
+ {
+ this.destination = destination;
+ this.maxConnections = maxConnections;
+ this.requester = requester;
+ }
+
+ @ManagedAttribute(value = "The max number of connections", readonly = true)
+ public int getMaxConnectionCount()
+ {
+ return maxConnections;
+ }
+
+ @ManagedAttribute(value = "The number of connections", readonly = true)
+ public int getConnectionCount()
+ {
+ return connectionCount.get();
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return connectionCount.get() == 0;
+ }
+
+ @Override
+ public boolean isClosed()
+ {
+ return closed.get();
+ }
+
+ @Override
+ public Connection acquire()
+ {
+ Connection connection = activate();
+ if (connection == null)
+ connection = tryCreate();
+ return connection;
+ }
+
+ private Connection tryCreate()
+ {
+ while (true)
+ {
+ int current = getConnectionCount();
+ final int next = current + 1;
+
+ if (next > maxConnections)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Max connections {}/{} reached", current, maxConnections);
+ // Try again the idle connections
+ return activate();
+ }
+
+ if (connectionCount.compareAndSet(current, next))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection {}/{} creation", next, maxConnections);
+
+ destination.newConnection(new Promise()
+ {
+ @Override
+ public void succeeded(Connection connection)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection {}/{} creation succeeded {}", next, maxConnections, connection);
+ onCreated(connection);
+ proceed();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection " + next + "/" + maxConnections + " creation failed", x);
+ connectionCount.decrementAndGet();
+ requester.failed(x);
+ }
+ });
+
+ // Try again the idle connections
+ return activate();
+ }
+ }
+ }
+
+ protected abstract void onCreated(Connection connection);
+
+ protected void proceed()
+ {
+ requester.succeeded();
+ }
+
+ protected abstract Connection activate();
+
+ protected Connection active(Connection connection)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection active {}", connection);
+ acquired(connection);
+ return connection;
+ }
+
+ protected void acquired(Connection connection)
+ {
+ }
+
+ protected boolean idle(Connection connection, boolean close)
+ {
+ if (close)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection idle close {}", connection);
+ return false;
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection idle {}", connection);
+ return true;
+ }
+ }
+
+ protected void released(Connection connection)
+ {
+ }
+
+ protected void removed(Connection connection)
+ {
+ int pooled = connectionCount.decrementAndGet();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Connection removed {} - pooled: {}", connection, pooled);
+ }
+
+ @Override
+ public void close()
+ {
+ if (closed.compareAndSet(false, true))
+ {
+ connectionCount.set(0);
+ }
+ }
+
+ protected void close(Collection connections)
+ {
+ connections.forEach(Connection::close);
+ }
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
index f4e9d90f7e3..81959031f3f 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpClientTransport.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
+import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Map;
@@ -31,6 +32,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@@ -173,13 +175,15 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
}
@Override
- protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
+ protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
- return new SelectChannelEndPoint(channel, selector, key, getScheduler(), client.getIdleTimeout());
+ SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler());
+ endp.setIdleTimeout(client.getIdleTimeout());
+ return endp;
}
@Override
- public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
+ public org.eclipse.jetty.io.Connection newConnection(SelectableChannel channel, EndPoint endPoint, Object attachment) throws IOException
{
@SuppressWarnings("unchecked")
Map context = (Map)attachment;
@@ -188,7 +192,7 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
}
@Override
- protected void connectionFailed(SocketChannel channel, Throwable x, Object attachment)
+ protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment)
{
@SuppressWarnings("unchecked")
Map context = (Map)attachment;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index 9e7f684620a..d25903e58a3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -37,7 +37,7 @@ import org.eclipse.jetty.util.log.Logger;
public abstract class AuthenticationProtocolHandler implements ProtocolHandler
{
- public static final int DEFAULT_MAX_CONTENT_LENGTH = 4096;
+ public static final int DEFAULT_MAX_CONTENT_LENGTH = 16*1024;
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
private static final Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
index ecf45697e1a..029a388d330 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ConnectionPool.java
@@ -19,424 +19,23 @@
package org.eclipse.jetty.client;
import java.io.Closeable;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.Destination;
-import org.eclipse.jetty.util.BlockingArrayQueue;
-import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.Promise;
-import org.eclipse.jetty.util.annotation.ManagedAttribute;
-import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.util.component.ContainerLifeCycle;
-import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.thread.Sweeper;
-@ManagedObject("The connection pool")
-public class ConnectionPool implements Closeable, Dumpable, Sweeper.Sweepable
+public interface ConnectionPool extends Closeable
{
- private static final Logger LOG = Log.getLogger(ConnectionPool.class);
+ boolean isActive(Connection connection);
- private final AtomicInteger connectionCount = new AtomicInteger();
- private final ReentrantLock lock = new ReentrantLock();
- private final Destination destination;
- private final int maxConnections;
- private final Callback requester;
- private final Deque idleConnections;
- private final Queue activeConnections;
+ boolean isEmpty();
- public ConnectionPool(Destination destination, int maxConnections, Callback requester)
- {
- this.destination = destination;
- this.maxConnections = maxConnections;
- this.requester = requester;
- this.idleConnections = new LinkedBlockingDeque<>(maxConnections);
- this.activeConnections = new BlockingArrayQueue<>(maxConnections);
- }
+ boolean isClosed();
- @ManagedAttribute(value = "The number of connections", readonly = true)
- public int getConnectionCount()
- {
- return connectionCount.get();
- }
+ Connection acquire();
- @ManagedAttribute(value = "The number of idle connections", readonly = true)
- public int getIdleConnectionCount()
- {
- return idleConnections.size();
- }
+ boolean release(Connection connection);
- @ManagedAttribute(value = "The number of active connections", readonly = true)
- public int getActiveConnectionCount()
- {
- return activeConnections.size();
- }
-
- public Queue getIdleConnections()
- {
- return idleConnections;
- }
-
- public Queue getActiveConnections()
- {
- return activeConnections;
- }
-
- public Connection acquire()
- {
- Connection connection = activateIdle();
- if (connection == null)
- connection = tryCreate();
- return connection;
- }
-
- private Connection tryCreate()
- {
- while (true)
- {
- int current = getConnectionCount();
- final int next = current + 1;
-
- if (next > maxConnections)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Max connections {}/{} reached", current, maxConnections);
- // Try again the idle connections
- return activateIdle();
- }
-
- if (connectionCount.compareAndSet(current, next))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection {}/{} creation", next, maxConnections);
-
- destination.newConnection(new Promise()
- {
- @Override
- public void succeeded(Connection connection)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection {}/{} creation succeeded {}", next, maxConnections, connection);
-
- idleCreated(connection);
-
- proceed();
- }
-
- @Override
- public void failed(Throwable x)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection " + next + "/" + maxConnections + " creation failed", x);
-
- connectionCount.decrementAndGet();
-
- requester.failed(x);
- }
- });
-
- // Try again the idle connections
- return activateIdle();
- }
- }
- }
-
- protected void proceed()
- {
- requester.succeeded();
- }
-
- protected void idleCreated(Connection connection)
- {
- boolean idle;
- lock();
- try
- {
- // Use "cold" new connections as last.
- idle = idleConnections.offerLast(connection);
- }
- finally
- {
- unlock();
- }
-
- idle(connection, idle);
- }
-
- private Connection activateIdle()
- {
- boolean acquired;
- Connection connection;
- lock();
- try
- {
- connection = idleConnections.pollFirst();
- if (connection == null)
- return null;
- acquired = activeConnections.offer(connection);
- }
- finally
- {
- unlock();
- }
-
- if (acquired)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection active {}", connection);
- acquired(connection);
- return connection;
- }
- else
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection active overflow {}", connection);
- connection.close();
- return null;
- }
- }
-
- protected void acquired(Connection connection)
- {
- }
-
- public boolean release(Connection connection)
- {
- boolean idle;
- lock();
- try
- {
- if (!activeConnections.remove(connection))
- return false;
- // Make sure we use "hot" connections first.
- idle = offerIdle(connection);
- }
- finally
- {
- unlock();
- }
-
- released(connection);
- return idle(connection, idle);
- }
-
- protected boolean offerIdle(Connection connection)
- {
- return idleConnections.offerFirst(connection);
- }
-
- protected boolean idle(Connection connection, boolean idle)
- {
- if (idle)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection idle {}", connection);
- return true;
- }
- else
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Connection idle overflow {}", connection);
- connection.close();
- return false;
- }
- }
-
- protected void released(Connection connection)
- {
- }
-
- public boolean remove(Connection connection)
- {
- return remove(connection, false);
- }
-
- protected boolean remove(Connection connection, boolean force)
- {
- boolean activeRemoved;
- boolean idleRemoved;
- lock();
- try
- {
- activeRemoved = activeConnections.remove(connection);
- idleRemoved = idleConnections.remove(connection);
- }
- finally
- {
- unlock();
- }
-
- if (activeRemoved)
- released(connection);
- boolean removed = activeRemoved || idleRemoved || force;
- if (removed)
- {
- int pooled = connectionCount.decrementAndGet();
- if (LOG.isDebugEnabled())
- LOG.debug("Connection removed {} - pooled: {}", connection, pooled);
- }
- return removed;
- }
-
- public boolean isActive(Connection connection)
- {
- lock();
- try
- {
- return activeConnections.contains(connection);
- }
- finally
- {
- unlock();
- }
- }
-
- public boolean isIdle(Connection connection)
- {
- lock();
- try
- {
- return idleConnections.contains(connection);
- }
- finally
- {
- unlock();
- }
- }
-
- public boolean isEmpty()
- {
- return connectionCount.get() == 0;
- }
-
- public void close()
- {
- List idles = new ArrayList<>();
- List actives = new ArrayList<>();
- lock();
- try
- {
- idles.addAll(idleConnections);
- idleConnections.clear();
- actives.addAll(activeConnections);
- activeConnections.clear();
- }
- finally
- {
- unlock();
- }
-
- connectionCount.set(0);
-
- for (Connection connection : idles)
- connection.close();
-
- // A bit drastic, but we cannot wait for all requests to complete
- for (Connection connection : actives)
- connection.close();
- }
+ boolean remove(Connection connection);
@Override
- public String dump()
- {
- return ContainerLifeCycle.dump(this);
- }
-
- @Override
- public void dump(Appendable out, String indent) throws IOException
- {
- List actives = new ArrayList<>();
- List idles = new ArrayList<>();
- lock();
- try
- {
- actives.addAll(activeConnections);
- idles.addAll(idleConnections);
- }
- finally
- {
- unlock();
- }
-
- ContainerLifeCycle.dumpObject(out, this);
- ContainerLifeCycle.dump(out, indent, actives, idles);
- }
-
- @Override
- public boolean sweep()
- {
- List toSweep = new ArrayList<>();
- lock();
- try
- {
- for (Connection connection : getActiveConnections())
- {
- if (connection instanceof Sweeper.Sweepable)
- toSweep.add(((Sweeper.Sweepable)connection));
- }
- }
- finally
- {
- unlock();
- }
-
- for (Sweeper.Sweepable candidate : toSweep)
- {
- if (candidate.sweep())
- {
- boolean removed = getActiveConnections().remove(candidate);
- LOG.warn("Connection swept: {}{}{} from active connections{}{}",
- candidate,
- System.lineSeparator(),
- removed ? "Removed" : "Not removed",
- System.lineSeparator(),
- dump());
- }
- }
-
- return false;
- }
-
- protected void lock()
- {
- lock.lock();
- }
-
- protected void unlock()
- {
- lock.unlock();
- }
-
- @Override
- public String toString()
- {
- int activeSize;
- int idleSize;
- lock();
- try
- {
- activeSize = activeConnections.size();
- idleSize = idleConnections.size();
- }
- finally
- {
- unlock();
- }
-
- return String.format("%s[c=%d/%d,a=%d,i=%d]",
- getClass().getSimpleName(),
- connectionCount.get(),
- maxConnections,
- activeSize,
- idleSize);
- }
+ void close();
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java
new file mode 100644
index 00000000000..c22966c3724
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/DuplexConnectionPool.java
@@ -0,0 +1,313 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Sweeper;
+
+@ManagedObject("The connection pool")
+public class DuplexConnectionPool extends AbstractConnectionPool implements Dumpable, Sweeper.Sweepable
+{
+ private static final Logger LOG = Log.getLogger(DuplexConnectionPool.class);
+
+ private final ReentrantLock lock = new ReentrantLock();
+ private final Deque idleConnections;
+ private final Set activeConnections;
+
+ public DuplexConnectionPool(Destination destination, int maxConnections, Callback requester)
+ {
+ super(destination, maxConnections, requester);
+ this.idleConnections = new ArrayDeque<>(maxConnections);
+ this.activeConnections = new HashSet<>(maxConnections);
+ }
+
+ protected void lock()
+ {
+ lock.lock();
+ }
+
+ protected void unlock()
+ {
+ lock.unlock();
+ }
+
+ @ManagedAttribute(value = "The number of idle connections", readonly = true)
+ public int getIdleConnectionCount()
+ {
+ lock();
+ try
+ {
+ return idleConnections.size();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ @ManagedAttribute(value = "The number of active connections", readonly = true)
+ public int getActiveConnectionCount()
+ {
+ lock();
+ try
+ {
+ return activeConnections.size();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ public Queue getIdleConnections()
+ {
+ return idleConnections;
+ }
+
+ public Collection getActiveConnections()
+ {
+ return activeConnections;
+ }
+
+ @Override
+ public boolean isActive(Connection connection)
+ {
+ lock();
+ try
+ {
+ return activeConnections.contains(connection);
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ @Override
+ protected void onCreated(Connection connection)
+ {
+ lock();
+ try
+ {
+ // Use "cold" new connections as last.
+ idleConnections.offer(connection);
+ }
+ finally
+ {
+ unlock();
+ }
+
+ idle(connection, false);
+ }
+
+ @Override
+ protected Connection activate()
+ {
+ Connection connection;
+ lock();
+ try
+ {
+ connection = idleConnections.poll();
+ if (connection == null)
+ return null;
+ activeConnections.add(connection);
+ }
+ finally
+ {
+ unlock();
+ }
+
+ return active(connection);
+ }
+
+ public boolean release(Connection connection)
+ {
+ boolean closed = isClosed();
+ lock();
+ try
+ {
+ if (!activeConnections.remove(connection))
+ return false;
+
+ if (!closed)
+ {
+ // Make sure we use "hot" connections first.
+ deactivate(connection);
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+
+ released(connection);
+ return idle(connection, closed);
+ }
+
+ protected boolean deactivate(Connection connection)
+ {
+ return idleConnections.offerFirst(connection);
+ }
+
+ public boolean remove(Connection connection)
+ {
+ return remove(connection, false);
+ }
+
+ protected boolean remove(Connection connection, boolean force)
+ {
+ boolean activeRemoved;
+ boolean idleRemoved;
+ lock();
+ try
+ {
+ activeRemoved = activeConnections.remove(connection);
+ idleRemoved = idleConnections.remove(connection);
+ }
+ finally
+ {
+ unlock();
+ }
+
+ if (activeRemoved || force)
+ released(connection);
+ boolean removed = activeRemoved || idleRemoved || force;
+ if (removed)
+ removed(connection);
+ return removed;
+ }
+
+ public void close()
+ {
+ super.close();
+
+ List connections = new ArrayList<>();
+ lock();
+ try
+ {
+ connections.addAll(idleConnections);
+ idleConnections.clear();
+ connections.addAll(activeConnections);
+ activeConnections.clear();
+ }
+ finally
+ {
+ unlock();
+ }
+
+ close(connections);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ List connections = new ArrayList<>();
+ lock();
+ try
+ {
+ connections.addAll(activeConnections);
+ connections.addAll(idleConnections);
+ }
+ finally
+ {
+ unlock();
+ }
+
+ ContainerLifeCycle.dumpObject(out, this);
+ ContainerLifeCycle.dump(out, indent, connections);
+ }
+
+ @Override
+ public boolean sweep()
+ {
+ List toSweep = new ArrayList<>();
+ lock();
+ try
+ {
+ for (Connection connection : activeConnections)
+ {
+ if (connection instanceof Sweeper.Sweepable)
+ toSweep.add(connection);
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+
+ for (Connection connection : toSweep)
+ {
+ if (((Sweeper.Sweepable)connection).sweep())
+ {
+ boolean removed = remove(connection, true);
+ LOG.warn("Connection swept: {}{}{} from active connections{}{}",
+ connection,
+ System.lineSeparator(),
+ removed ? "Removed" : "Not removed",
+ System.lineSeparator(),
+ dump());
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ int activeSize;
+ int idleSize;
+ lock();
+ try
+ {
+ activeSize = activeConnections.size();
+ idleSize = idleConnections.size();
+ }
+ finally
+ {
+ unlock();
+ }
+
+ return String.format("%s[c=%d/%d,a=%d,i=%d]",
+ getClass().getSimpleName(),
+ getConnectionCount(),
+ getMaxConnectionCount(),
+ activeSize,
+ idleSize);
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
index d493a9e200f..09dd2fb3ad0 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpChannel.java
@@ -129,6 +129,11 @@ public abstract class HttpChannel
return getHttpReceiver().abort(exchange, failure);
}
+ public Result exchangeTerminating(HttpExchange exchange, Result result)
+ {
+ return result;
+ }
+
public void exchangeTerminated(HttpExchange exchange, Result result)
{
disassociate(exchange);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 93337516af7..cbf15b6fffd 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.client;
-import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.CookieStore;
@@ -498,24 +497,18 @@ public class HttpClient extends ContainerLifeCycle
if (destination == null)
{
destination = transport.newHttpDestination(origin);
- if (isRunning())
+ addManaged(destination);
+ HttpDestination existing = destinations.putIfAbsent(origin, destination);
+ if (existing != null)
{
- HttpDestination existing = destinations.putIfAbsent(origin, destination);
- if (existing != null)
- {
- destination = existing;
- }
- else
- {
- addManaged(destination);
- if (LOG.isDebugEnabled())
- LOG.debug("Created {}", destination);
- }
-
- if (!isRunning())
- removeDestination(destination);
+ removeBean(destination);
+ destination = existing;
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Created {}", destination);
}
-
}
return destination;
}
@@ -531,15 +524,12 @@ public class HttpClient extends ContainerLifeCycle
*/
public List getDestinations()
{
- return new ArrayList(destinations.values());
+ return new ArrayList<>(destinations.values());
}
protected void send(final HttpRequest request, List listeners)
{
String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
- if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme))
- throw new IllegalArgumentException("Invalid protocol " + scheme);
-
String host = request.getHost().toLowerCase(Locale.ENGLISH);
HttpDestination destination = destinationFor(scheme, host, request.getPort());
destination.send(request, listeners);
@@ -994,7 +984,7 @@ public class HttpClient extends ContainerLifeCycle
* anymore and leave space for new destinations.
*
* @param removeIdleDestinations whether destinations that have no connections should be removed
- * @see org.eclipse.jetty.client.ConnectionPool
+ * @see org.eclipse.jetty.client.DuplexConnectionPool
*/
public void setRemoveIdleDestinations(boolean removeIdleDestinations)
{
@@ -1047,19 +1037,25 @@ public class HttpClient extends ContainerLifeCycle
protected int normalizePort(String scheme, int port)
{
- return port > 0 ? port : HttpScheme.HTTPS.is(scheme) ? 443 : 80;
+ if (port > 0)
+ return port;
+ else if (isSchemeSecure(scheme))
+ return 443;
+ else
+ return 80;
}
public boolean isDefaultPort(String scheme, int port)
{
- return HttpScheme.HTTPS.is(scheme) ? port == 443 : port == 80;
+ if (isSchemeSecure(scheme))
+ return port == 443;
+ else
+ return port == 80;
}
- @Override
- public void dump(Appendable out, String indent) throws IOException
+ public boolean isSchemeSecure(String scheme)
{
- dumpThis(out);
- dump(out, indent, getBeans(), destinations.values());
+ return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme);
}
private class ContentDecoderFactorySet implements Set
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
index 58b16bb4ad3..1d01f0637c6 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
@@ -67,11 +67,13 @@ public class HttpContent implements Callback, Closeable
{
private static final Logger LOG = Log.getLogger(HttpContent.class);
private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
+ private static final ByteBuffer CLOSE = ByteBuffer.allocate(0);
private final ContentProvider provider;
private final Iterator iterator;
private ByteBuffer buffer;
- private volatile ByteBuffer content;
+ private ByteBuffer content;
+ private boolean last;
public HttpContent(ContentProvider provider)
{
@@ -92,7 +94,7 @@ public class HttpContent implements Callback, Closeable
*/
public boolean isLast()
{
- return !iterator.hasNext();
+ return last;
}
/**
@@ -124,41 +126,50 @@ public class HttpContent implements Callback, Closeable
*/
public boolean advance()
{
- boolean advanced;
- boolean hasNext;
- ByteBuffer bytes;
if (iterator instanceof Synchronizable)
{
synchronized (((Synchronizable)iterator).getLock())
{
- advanced = iterator.hasNext();
- bytes = advanced ? iterator.next() : null;
- hasNext = advanced && iterator.hasNext();
+ return advance(iterator);
}
}
else
{
- advanced = iterator.hasNext();
- bytes = advanced ? iterator.next() : null;
- hasNext = advanced && iterator.hasNext();
+ return advance(iterator);
}
+ }
- if (advanced)
+ private boolean advance(Iterator iterator)
+ {
+ boolean hasNext = iterator.hasNext();
+ ByteBuffer bytes = hasNext ? iterator.next() : null;
+ boolean hasMore = hasNext && iterator.hasNext();
+ boolean wasLast = last;
+ last = !hasMore;
+
+ if (hasNext)
{
buffer = bytes;
content = bytes == null ? null : bytes.slice();
if (LOG.isDebugEnabled())
- LOG.debug("Advanced content to {} chunk {}", hasNext ? "next" : "last", bytes);
+ LOG.debug("Advanced content to {} chunk {}", hasMore ? "next" : "last", String.valueOf(bytes));
return bytes != null;
}
else
{
- if (content != AFTER)
+ // No more content, but distinguish between last and consumed.
+ if (wasLast)
{
- content = buffer = AFTER;
+ buffer = content = AFTER;
if (LOG.isDebugEnabled())
LOG.debug("Advanced content past last chunk");
}
+ else
+ {
+ buffer = content = CLOSE;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Advanced content to last chunk");
+ }
return false;
}
}
@@ -168,7 +179,7 @@ public class HttpContent implements Callback, Closeable
*/
public boolean isConsumed()
{
- return content == AFTER;
+ return buffer == AFTER;
}
@Override
@@ -176,6 +187,8 @@ public class HttpContent implements Callback, Closeable
{
if (isConsumed())
return;
+ if (buffer == CLOSE)
+ return;
if (iterator instanceof Callback)
((Callback)iterator).succeeded();
}
@@ -185,6 +198,8 @@ public class HttpContent implements Callback, Closeable
{
if (isConsumed())
return;
+ if (buffer == CLOSE)
+ return;
if (iterator instanceof Callback)
((Callback)iterator).failed(x);
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
index 9031562db21..58eaee5ef51 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java
@@ -22,19 +22,21 @@ import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.AsynchronousCloseException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
+import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@@ -42,9 +44,10 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Sweeper;
@ManagedObject
-public abstract class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Dumpable
+public abstract class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable
{
protected static final Logger LOG = Log.getLogger(HttpDestination.class);
@@ -56,6 +59,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
private final ProxyConfiguration.Proxy proxy;
private final ClientConnectionFactory connectionFactory;
private final HttpField hostField;
+ private ConnectionPool connectionPool;
public HttpDestination(HttpClient client, Origin origin)
{
@@ -76,7 +80,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
}
else
{
- if (HttpScheme.HTTPS.is(getScheme()))
+ if (isSecure())
connectionFactory = newSslClientConnectionFactory(connectionFactory);
}
this.connectionFactory = connectionFactory;
@@ -87,6 +91,29 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
hostField = new HttpField(HttpHeader.HOST, host);
}
+ @Override
+ protected void doStart() throws Exception
+ {
+ this.connectionPool = newConnectionPool(client);
+ addBean(connectionPool);
+ super.doStart();
+ Sweeper sweeper = client.getBean(Sweeper.class);
+ if (sweeper != null && connectionPool instanceof Sweeper.Sweepable)
+ sweeper.offer((Sweeper.Sweepable)connectionPool);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ Sweeper sweeper = client.getBean(Sweeper.class);
+ if (sweeper != null && connectionPool instanceof Sweeper.Sweepable)
+ sweeper.remove((Sweeper.Sweepable)connectionPool);
+ super.doStop();
+ removeBean(connectionPool);
+ }
+
+ protected abstract ConnectionPool newConnectionPool(HttpClient client);
+
protected Queue newExchangeQueue(HttpClient client)
{
return new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
@@ -97,6 +124,11 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
}
+ public boolean isSecure()
+ {
+ return client.isSchemeSecure(getScheme());
+ }
+
public HttpClient getHttpClient()
{
return client;
@@ -171,6 +203,24 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
return hostField;
}
+ @ManagedAttribute(value = "The connection pool", readonly = true)
+ public ConnectionPool getConnectionPool()
+ {
+ return connectionPool;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ send();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ abort(x);
+ }
+
protected void send(HttpRequest request, List listeners)
{
if (!getScheme().equalsIgnoreCase(request.getScheme()))
@@ -217,7 +267,59 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
return queue.offer(exchange);
}
- public abstract void send();
+ public void send()
+ {
+ if (getHttpExchanges().isEmpty())
+ return;
+ process();
+ }
+
+ private void process()
+ {
+ Connection connection = connectionPool.acquire();
+ if (connection != null)
+ process(connection);
+ }
+
+ public void process(final Connection connection)
+ {
+ HttpClient client = getHttpClient();
+ final HttpExchange exchange = getHttpExchanges().poll();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Processing exchange {} on {} of {}", exchange, connection, this);
+ if (exchange == null)
+ {
+ if (!connectionPool.release(connection))
+ connection.close();
+
+ if (!client.isRunning())
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} is stopping", client);
+ connection.close();
+ }
+ }
+ else
+ {
+ final Request request = exchange.getRequest();
+ Throwable cause = request.getAbortCause();
+ if (cause != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Aborted before processing {}: {}", exchange, cause);
+ // It may happen that the request is aborted before the exchange
+ // is created. Aborting the exchange a second time will result in
+ // a no-operation, so we just abort here to cover that edge case.
+ exchange.abort(cause);
+ }
+ else
+ {
+ send(connection, exchange);
+ }
+ }
+ }
+
+ protected abstract void send(Connection connection, HttpExchange exchange);
public void newConnection(Promise promise)
{
@@ -239,14 +341,67 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
abort(new AsynchronousCloseException());
if (LOG.isDebugEnabled())
LOG.debug("Closed {}", this);
+ connectionPool.close();
}
public void release(Connection connection)
{
+ if (LOG.isDebugEnabled())
+ LOG.debug("Released {}", connection);
+ HttpClient client = getHttpClient();
+ if (client.isRunning())
+ {
+ if (connectionPool.isActive(connection))
+ {
+ if (connectionPool.release(connection))
+ send();
+ else
+ connection.close();
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Released explicit {}", connection);
+ }
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} is stopped", client);
+ connection.close();
+ }
+ }
+
+ public boolean remove(Connection connection)
+ {
+ return connectionPool.remove(connection);
}
public void close(Connection connection)
{
+ boolean removed = remove(connection);
+
+ if (getHttpExchanges().isEmpty())
+ {
+ if (getHttpClient().isRemoveIdleDestinations() && connectionPool.isEmpty())
+ {
+ // There is a race condition between this thread removing the destination
+ // and another thread queueing a request to this same destination.
+ // If this destination is removed, but the request queued, a new connection
+ // will be opened, the exchange will be executed and eventually the connection
+ // will idle timeout and be closed. Meanwhile a new destination will be created
+ // in HttpClient and will be used for other requests.
+ getHttpClient().removeDestination(this);
+ }
+ }
+ else
+ {
+ // We need to execute queued requests even if this connection failed.
+ // We may create a connection that is not needed, but it will eventually
+ // idle timeout, so no worries.
+ if (removed)
+ process();
+ }
}
/**
@@ -274,6 +429,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
public void dump(Appendable out, String indent) throws IOException
{
ContainerLifeCycle.dumpObject(out, toString());
+ ContainerLifeCycle.dump(out, indent, Collections.singletonList(connectionPool));
}
public String asString()
@@ -284,11 +440,12 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
@Override
public String toString()
{
- return String.format("%s[%s]%x%s,queue=%d",
+ return String.format("%s[%s]%x%s,queue=%d,pool=%s",
HttpDestination.class.getSimpleName(),
asString(),
hashCode(),
proxy == null ? "" : "(via " + proxy + ")",
- exchanges.size());
+ exchanges.size(),
+ connectionPool);
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
index c46bc6cd35b..2a92a5d42ab 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
@@ -26,7 +26,6 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -108,7 +107,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
public void succeeded(Connection connection)
{
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
- if (HttpScheme.HTTPS.is(destination.getScheme()))
+ if (destination.isSecure())
{
SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
if (sslContextFactory != null)
@@ -119,7 +118,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
{
String message = String.format("Cannot perform requests over SSL, no %s in %s",
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
- promise.failed(new IllegalStateException(message));
+ tunnelFailed(new IllegalStateException(message));
}
}
else
@@ -131,7 +130,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
@Override
public void failed(Throwable x)
{
- promise.failed(x);
+ tunnelFailed(x);
}
private void tunnel(HttpDestination destination, final Connection connection)
@@ -139,33 +138,31 @@ public class HttpProxy extends ProxyConfiguration.Proxy
String target = destination.getOrigin().getAddress().asString();
Origin.Address proxyAddress = destination.getConnectAddress();
HttpClient httpClient = destination.getHttpClient();
+ long connectTimeout = httpClient.getConnectTimeout();
Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.CONNECT)
.path(target)
.header(HttpHeader.HOST, target)
- .timeout(httpClient.getConnectTimeout(), TimeUnit.MILLISECONDS);
+ .idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS)
+ .timeout(connectTimeout, TimeUnit.MILLISECONDS);
- connection.send(connect, new Response.CompleteListener()
+ connection.send(connect, result ->
{
- @Override
- public void onComplete(Result result)
+ if (result.isFailed())
{
- if (result.isFailed())
+ tunnelFailed(result.getFailure());
+ }
+ else
+ {
+ Response response = result.getResponse();
+ if (response.getStatus() == 200)
{
- tunnelFailed(result.getFailure());
+ tunnelSucceeded();
}
else
{
- Response response = result.getResponse();
- if (response.getStatus() == 200)
- {
- tunnelSucceeded();
- }
- else
- {
- tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
- }
+ tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
}
}
});
@@ -182,10 +179,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
- Helper.replaceConnection(oldConnection, newConnection);
- // Avoid setting fill interest in the old Connection,
- // without closing the underlying EndPoint.
- oldConnection.softClose();
+ endPoint.upgrade(newConnection);
if (LOG.isDebugEnabled())
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
}
@@ -198,7 +192,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
private void tunnelFailed(Throwable failure)
{
endPoint.close();
- failed(failure);
+ promise.failed(failure);
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index 2fb444130c8..04bdf627313 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -437,6 +437,7 @@ public abstract class HttpReceiver
if (result != null)
{
+ result = channel.exchangeTerminating(exchange, result);
boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
if (!ordered)
channel.exchangeTerminated(exchange, result);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java
index 37fcb97574b..da1c95ae36a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequestException.java
@@ -20,7 +20,7 @@ package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Request;
-public class HttpRequestException extends Throwable
+public class HttpRequestException extends RuntimeException
{
private final Request request;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index 650587e48c1..25fd7d94878 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -376,6 +376,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
}
else
{
+ result = channel.exchangeTerminating(exchange, result);
HttpDestination destination = getHttpChannel().getHttpDestination();
boolean ordered = destination.getHttpClient().isStrictEventOrdering();
if (!ordered)
@@ -678,7 +679,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
{
return content.isNonBlocking();
}
-
+
@Override
public void succeeded()
{
@@ -811,9 +812,9 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
while (true)
{
boolean advanced = content.advance();
- boolean consumed = content.isConsumed();
+ boolean lastContent = content.isLast();
if (LOG.isDebugEnabled())
- LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest());
+ LOG.debug("Content present {}, last {}, consumed {} for {}", advanced, lastContent, content.isConsumed(), exchange.getRequest());
if (advanced)
{
@@ -821,7 +822,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
return Action.SCHEDULED;
}
- if (consumed)
+ if (lastContent)
{
sendContent(exchange, content, lastCallback);
return Action.IDLE;
@@ -894,7 +895,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
{
return content.isNonBlocking();
}
-
+
@Override
public void succeeded()
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java
index 7762af09f94..f5d3b98580e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/LeakTrackingConnectionPool.java
@@ -25,7 +25,7 @@ import org.eclipse.jetty.util.LeakDetector;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-public class LeakTrackingConnectionPool extends ConnectionPool
+public class LeakTrackingConnectionPool extends DuplexConnectionPool
{
private static final Logger LOG = Log.getLogger(LeakTrackingConnectionPool.class);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java
new file mode 100644
index 00000000000..88561bb75bf
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexConnectionPool.java
@@ -0,0 +1,302 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class MultiplexConnectionPool extends AbstractConnectionPool
+{
+ private static final Logger LOG = Log.getLogger(MultiplexConnectionPool.class);
+
+ private final ReentrantLock lock = new ReentrantLock();
+ private final int maxMultiplexed;
+ private final Deque idleConnections;
+ private final Map muxedConnections;
+ private final Map busyConnections;
+
+ public MultiplexConnectionPool(HttpDestination destination, int maxConnections, Callback requester, int maxMultiplexed)
+ {
+ super(destination, maxConnections, requester);
+ this.maxMultiplexed = maxMultiplexed;
+ this.idleConnections = new ArrayDeque<>(maxConnections);
+ this.muxedConnections = new HashMap<>(maxConnections);
+ this.busyConnections = new HashMap<>(maxConnections);
+ }
+
+ protected void lock()
+ {
+ lock.lock();
+ }
+
+ protected void unlock()
+ {
+ lock.unlock();
+ }
+
+ @Override
+ public boolean isActive(Connection connection)
+ {
+ lock();
+ try
+ {
+ if (muxedConnections.containsKey(connection))
+ return true;
+ if (busyConnections.containsKey(connection))
+ return true;
+ return false;
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ @Override
+ protected void onCreated(Connection connection)
+ {
+ lock();
+ try
+ {
+ // Use "cold" connections as last.
+ idleConnections.offer(new Holder(connection));
+ }
+ finally
+ {
+ unlock();
+ }
+
+ idle(connection, false);
+ }
+
+ @Override
+ protected Connection activate()
+ {
+ Holder holder;
+ lock();
+ try
+ {
+ while (true)
+ {
+ if (muxedConnections.isEmpty())
+ {
+ holder = idleConnections.poll();
+ if (holder == null)
+ return null;
+ muxedConnections.put(holder.connection, holder);
+ }
+ else
+ {
+ holder = muxedConnections.values().iterator().next();
+ }
+
+ if (holder.count < maxMultiplexed)
+ {
+ ++holder.count;
+ break;
+ }
+ else
+ {
+ muxedConnections.remove(holder.connection);
+ busyConnections.put(holder.connection, holder);
+ }
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+
+ return active(holder.connection);
+ }
+
+ @Override
+ public boolean release(Connection connection)
+ {
+ boolean closed = isClosed();
+ boolean idle = false;
+ Holder holder;
+ lock();
+ try
+ {
+ holder = muxedConnections.get(connection);
+ if (holder != null)
+ {
+ int count = --holder.count;
+ if (count == 0)
+ {
+ muxedConnections.remove(connection);
+ if (!closed)
+ {
+ idleConnections.offerFirst(holder);
+ idle = true;
+ }
+ }
+ }
+ else
+ {
+ holder = busyConnections.remove(connection);
+ if (holder != null)
+ {
+ int count = --holder.count;
+ if (!closed)
+ {
+ if (count == 0)
+ {
+ idleConnections.offerFirst(holder);
+ idle = true;
+ }
+ else
+ {
+ muxedConnections.put(connection, holder);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+
+ if (holder == null)
+ return false;
+
+ released(connection);
+ if (idle || closed)
+ return idle(connection, closed);
+ return true;
+ }
+
+ @Override
+ public boolean remove(Connection connection)
+ {
+ return remove(connection, false);
+ }
+
+ protected boolean remove(Connection connection, boolean force)
+ {
+ boolean activeRemoved = true;
+ boolean idleRemoved = false;
+ lock();
+ try
+ {
+ Holder holder = muxedConnections.remove(connection);
+ if (holder == null)
+ holder = busyConnections.remove(connection);
+ if (holder == null)
+ {
+ activeRemoved = false;
+ for (Iterator iterator = idleConnections.iterator(); iterator.hasNext();)
+ {
+ holder = iterator.next();
+ if (holder.connection == connection)
+ {
+ idleRemoved = true;
+ iterator.remove();
+ break;
+ }
+ }
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+
+ if (activeRemoved || force)
+ released(connection);
+ boolean removed = activeRemoved || idleRemoved || force;
+ if (removed)
+ removed(connection);
+ return removed;
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+
+ List connections;
+ lock();
+ try
+ {
+ connections = idleConnections.stream().map(holder -> holder.connection).collect(Collectors.toList());
+ connections.addAll(muxedConnections.keySet());
+ connections.addAll(busyConnections.keySet());
+ }
+ finally
+ {
+ unlock();
+ }
+
+ close(connections);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ List connections = new ArrayList<>();
+ lock();
+ try
+ {
+ connections.addAll(busyConnections.values());
+ connections.addAll(muxedConnections.values());
+ connections.addAll(idleConnections);
+ }
+ finally
+ {
+ unlock();
+ }
+
+ ContainerLifeCycle.dumpObject(out, this);
+ ContainerLifeCycle.dump(out, indent, connections);
+ }
+
+ private static class Holder
+ {
+ private final Connection connection;
+ private int count;
+
+ private Holder(Connection connection)
+ {
+ this.connection = connection;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[%d]", connection, count);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
index a50131f1ed2..a23fb34a829 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/MultiplexHttpDestination.java
@@ -18,136 +18,16 @@
package org.eclipse.jetty.client;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.util.Promise;
-
-public abstract class MultiplexHttpDestination extends HttpDestination implements Promise
+public abstract class MultiplexHttpDestination extends HttpDestination
{
- private final AtomicReference connect = new AtomicReference<>(ConnectState.DISCONNECTED);
- private C connection;
-
protected MultiplexHttpDestination(HttpClient client, Origin origin)
{
super(client, origin);
}
- @Override
- public void send()
+ protected ConnectionPool newConnectionPool(HttpClient client)
{
- while (true)
- {
- ConnectState current = connect.get();
- switch (current)
- {
- case DISCONNECTED:
- {
- if (!connect.compareAndSet(current, ConnectState.CONNECTING))
- break;
- newConnection(this);
- return;
- }
- case CONNECTING:
- {
- // Waiting to connect, just return
- return;
- }
- case CONNECTED:
- {
- if (process(connection))
- break;
- return;
- }
- default:
- {
- abort(new IllegalStateException("Invalid connection state " + current));
- return;
- }
- }
- }
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void succeeded(Connection result)
- {
- C connection = this.connection = (C)result;
- if (connect.compareAndSet(ConnectState.CONNECTING, ConnectState.CONNECTED))
- {
- process(connection);
- }
- else
- {
- connection.close();
- failed(new IllegalStateException());
- }
- }
-
- @Override
- public void failed(Throwable x)
- {
- connect.set(ConnectState.DISCONNECTED);
- abort(x);
- }
-
- protected boolean process(final C connection)
- {
- HttpClient client = getHttpClient();
- final HttpExchange exchange = getHttpExchanges().poll();
- if (LOG.isDebugEnabled())
- LOG.debug("Processing {} on {}", exchange, connection);
- if (exchange == null)
- return false;
-
- final Request request = exchange.getRequest();
- Throwable cause = request.getAbortCause();
- if (cause != null)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Aborted before processing {}: {}", exchange, cause);
- // It may happen that the request is aborted before the exchange
- // is created. Aborting the exchange a second time will result in
- // a no-operation, so we just abort here to cover that edge case.
- exchange.abort(cause);
- }
- else
- {
- send(connection, exchange);
- }
- return true;
- }
-
- @Override
- public void close()
- {
- super.close();
- C connection = this.connection;
- if (connection != null)
- connection.close();
- }
-
- @Override
- public void close(Connection connection)
- {
- super.close(connection);
- while (true)
- {
- ConnectState current = connect.get();
- if (connect.compareAndSet(current, ConnectState.DISCONNECTED))
- {
- if (getHttpClient().isRemoveIdleDestinations())
- getHttpClient().removeDestination(this);
- break;
- }
- }
- }
-
- protected abstract void send(C connection, HttpExchange exchange);
-
- private enum ConnectState
- {
- DISCONNECTED, CONNECTING, CONNECTED
+ return new MultiplexConnectionPool(this, client.getMaxConnectionsPerDestination(), this,
+ client.getMaxRequestsQueuedPerDestination());
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
index 3030a976584..b77805aeae1 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java
@@ -18,200 +18,15 @@
package org.eclipse.jetty.client;
-import java.io.IOException;
-import java.util.Collections;
-
-import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.annotation.ManagedAttribute;
-import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.util.component.ContainerLifeCycle;
-import org.eclipse.jetty.util.thread.Sweeper;
-
-@ManagedObject
-public abstract class PoolingHttpDestination extends HttpDestination implements Callback
+public abstract class PoolingHttpDestination extends HttpDestination
{
- private final ConnectionPool connectionPool;
-
public PoolingHttpDestination(HttpClient client, Origin origin)
{
super(client, origin);
- this.connectionPool = newConnectionPool(client);
- addBean(connectionPool);
- Sweeper sweeper = client.getBean(Sweeper.class);
- if (sweeper != null)
- sweeper.offer(connectionPool);
}
protected ConnectionPool newConnectionPool(HttpClient client)
{
- return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
- }
-
- @ManagedAttribute(value = "The connection pool", readonly = true)
- public ConnectionPool getConnectionPool()
- {
- return connectionPool;
- }
-
- @Override
- public void succeeded()
- {
- send();
- }
-
- @Override
- public void failed(final Throwable x)
- {
- abort(x);
- }
-
- public void send()
- {
- if (getHttpExchanges().isEmpty())
- return;
- process();
- }
-
- @SuppressWarnings("unchecked")
- public C acquire()
- {
- return (C)connectionPool.acquire();
- }
-
- private void process()
- {
- C connection = acquire();
- if (connection != null)
- process(connection);
- }
-
- /**
- *
Processes a new connection making it idle or active depending on whether requests are waiting to be sent.
- *
A new connection is created when a request needs to be executed; it is possible that the request that
- * triggered the request creation is executed by another connection that was just released, so the new connection
- * may become idle.
- *
If a request is waiting to be executed, it will be dequeued and executed by the new connection.
- *
- * @param connection the new connection
- */
- public void process(final C connection)
- {
- HttpClient client = getHttpClient();
- final HttpExchange exchange = getHttpExchanges().poll();
- if (LOG.isDebugEnabled())
- LOG.debug("Processing exchange {} on {} of {}", exchange, connection, this);
- if (exchange == null)
- {
- if (!connectionPool.release(connection))
- connection.close();
-
- if (!client.isRunning())
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} is stopping", client);
- connection.close();
- }
- }
- else
- {
- final Request request = exchange.getRequest();
- Throwable cause = request.getAbortCause();
- if (cause != null)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Aborted before processing {}: {}", exchange, cause);
- // It may happen that the request is aborted before the exchange
- // is created. Aborting the exchange a second time will result in
- // a no-operation, so we just abort here to cover that edge case.
- exchange.abort(cause);
- }
- else
- {
- send(connection, exchange);
- }
- }
- }
-
- protected abstract void send(C connection, HttpExchange exchange);
-
- @Override
- public void release(Connection c)
- {
- @SuppressWarnings("unchecked")
- C connection = (C)c;
- if (LOG.isDebugEnabled())
- LOG.debug("Released {}", connection);
- HttpClient client = getHttpClient();
- if (client.isRunning())
- {
- if (connectionPool.isActive(connection))
- {
- if (connectionPool.release(connection))
- send();
- else
- connection.close();
- }
- else
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Released explicit {}", connection);
- }
- }
- else
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} is stopped", client);
- connection.close();
- }
- }
-
- @Override
- public void close(Connection oldConnection)
- {
- super.close(oldConnection);
-
- connectionPool.remove(oldConnection);
-
- if (getHttpExchanges().isEmpty())
- {
- if (getHttpClient().isRemoveIdleDestinations() && connectionPool.isEmpty())
- {
- // There is a race condition between this thread removing the destination
- // and another thread queueing a request to this same destination.
- // If this destination is removed, but the request queued, a new connection
- // will be opened, the exchange will be executed and eventually the connection
- // will idle timeout and be closed. Meanwhile a new destination will be created
- // in HttpClient and will be used for other requests.
- getHttpClient().removeDestination(this);
- }
- }
- else
- {
- // We need to execute queued requests even if this connection failed.
- // We may create a connection that is not needed, but it will eventually
- // idle timeout, so no worries.
- process();
- }
- }
-
- public void close()
- {
- super.close();
- connectionPool.close();
- }
-
- @Override
- public void dump(Appendable out, String indent) throws IOException
- {
- super.dump(out, indent);
- ContainerLifeCycle.dump(out, indent, Collections.singletonList(connectionPool));
- }
-
- @Override
- public String toString()
- {
- return String.format("%s,pool=%s", super.toString(), connectionPool);
+ return new DuplexConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
index 7d6d48a2e5f..5c0969d3a88 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java
@@ -210,7 +210,7 @@ public class ResponseNotifier
notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
// TODO: handle callback
- notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
+ notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), Callback.NOOP);
notifySuccess(listeners, response);
}
@@ -232,7 +232,7 @@ public class ResponseNotifier
notifyHeaders(listeners, response);
if (response instanceof ContentResponse)
// TODO: handle callback
- notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter());
+ notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), Callback.NOOP);
notifyFailure(listeners, response, failure);
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
index 456e444e959..246681e646a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
@@ -27,7 +27,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
@@ -196,12 +195,12 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
HttpClient client = destination.getHttpClient();
ClientConnectionFactory connectionFactory = this.connectionFactory;
- if (HttpScheme.HTTPS.is(destination.getScheme()))
+ if (destination.isSecure())
connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
- org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(getEndPoint(), context);
- ClientConnectionFactory.Helper.replaceConnection(this, connection);
+ org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context);
+ getEndPoint().upgrade(newConnection);
if (LOG.isDebugEnabled())
- LOG.debug("SOCKS4 tunnel established: {} over {}", this, connection);
+ LOG.debug("SOCKS4 tunnel established: {} over {}", this, newConnection);
}
catch (Throwable x)
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java
index 9ddc97a4f0c..a218d14e87f 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ValidatingConnectionPool.java
@@ -34,7 +34,7 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
/**
- *
A {@link ConnectionPool} that validates connections before
+ *
A connection pool that validates connections before
* making them available for use.
*
Connections that have just been opened are not validated.
* Connections that are {@link #release(Connection) released} will
@@ -56,7 +56,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
* tuning the idle timeout of the servers to be larger than
* that of the client.
*/
-public class ValidatingConnectionPool extends ConnectionPool
+public class ValidatingConnectionPool extends DuplexConnectionPool
{
private static final Logger LOG = Log.getLogger(ValidatingConnectionPool.class);
@@ -154,7 +154,7 @@ public class ValidatingConnectionPool extends ConnectionPool
private class Holder implements Runnable
{
private final long timestamp = System.nanoTime();
- private final AtomicBoolean latch = new AtomicBoolean();
+ private final AtomicBoolean done = new AtomicBoolean();
private final Connection connection;
public Scheduler.Task task;
@@ -166,30 +166,31 @@ public class ValidatingConnectionPool extends ConnectionPool
@Override
public void run()
{
- if (latch.compareAndSet(false, true))
+ if (done.compareAndSet(false, true))
{
- boolean idle;
+ boolean closed = isClosed();
lock();
try
{
- quarantine.remove(connection);
- idle = offerIdle(connection);
if (LOG.isDebugEnabled())
LOG.debug("Validated {}", connection);
+ quarantine.remove(connection);
+ if (!closed)
+ deactivate(connection);
}
finally
{
unlock();
}
- if (idle(connection, idle))
- proceed();
+ idle(connection, closed);
+ proceed();
}
}
public boolean cancel()
{
- if (latch.compareAndSet(false, true))
+ if (done.compareAndSet(false, true))
{
task.cancel();
return true;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java
index a782bd8d884..f934ba5f0e4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Result.java
@@ -52,6 +52,14 @@ public class Result
this.responseFailure = responseFailure;
}
+ public Result(Result result, Throwable responseFailure)
+ {
+ this.request = result.request;
+ this.requestFailure = result.requestFailure;
+ this.response = result.response;
+ this.responseFailure = responseFailure;
+ }
+
/**
* @return the request object
*/
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
index a204cf3a2e2..0ca65b109cc 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java
@@ -18,16 +18,20 @@
package org.eclipse.jetty.client.http;
+import java.util.Locale;
+
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpExchange;
-import org.eclipse.jetty.client.HttpReceiver;
-import org.eclipse.jetty.client.HttpSender;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.client.HttpResponseException;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
public class HttpChannelOverHTTP extends HttpChannel
@@ -55,13 +59,13 @@ public class HttpChannelOverHTTP extends HttpChannel
}
@Override
- protected HttpSender getHttpSender()
+ protected HttpSenderOverHTTP getHttpSender()
{
return sender;
}
@Override
- protected HttpReceiver getHttpReceiver()
+ protected HttpReceiverOverHTTP getHttpReceiver()
{
return receiver;
}
@@ -85,6 +89,42 @@ public class HttpChannelOverHTTP extends HttpChannel
connection.release();
}
+ @Override
+ public Result exchangeTerminating(HttpExchange exchange, Result result)
+ {
+ if (result.isFailed())
+ return result;
+
+ HttpResponse response = exchange.getResponse();
+
+ if ((response.getVersion() == HttpVersion.HTTP_1_1) &&
+ (response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101))
+ {
+ String connection = response.getHeaders().get(HttpHeader.CONNECTION);
+ if ((connection == null) || !connection.toLowerCase(Locale.US).contains("upgrade"))
+ {
+ return new Result(result,new HttpResponseException("101 Switching Protocols without Connection: Upgrade not supported",response));
+ }
+
+ // Upgrade Response
+ HttpRequest request = exchange.getRequest();
+ if (request instanceof HttpConnectionUpgrader)
+ {
+ HttpConnectionUpgrader listener = (HttpConnectionUpgrader)request;
+ try
+ {
+ listener.upgrade(response,getHttpConnection());
+ }
+ catch (Throwable x)
+ {
+ return new Result(result,x);
+ }
+ }
+ }
+
+ return result;
+ }
+
public void receive()
{
receiver.receive();
@@ -131,7 +171,10 @@ public class HttpChannelOverHTTP extends HttpChannel
}
else
{
- release();
+ if (response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
+ connection.remove();
+ else
+ release();
}
}
@@ -143,4 +186,5 @@ public class HttpChannelOverHTTP extends HttpChannel
sender,
receiver);
}
+
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
index da9fcd6e087..5c60de2c66e 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionOverHTTP.java
@@ -18,6 +18,7 @@
package org.eclipse.jetty.client.http;
+import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -36,13 +37,13 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Sweeper;
-public class HttpConnectionOverHTTP extends AbstractConnection implements Connection, Sweeper.Sweepable
+public class HttpConnectionOverHTTP extends AbstractConnection implements Connection, org.eclipse.jetty.io.Connection.UpgradeFrom, Sweeper.Sweepable
{
private static final Logger LOG = Log.getLogger(HttpConnectionOverHTTP.class);
private final AtomicBoolean closed = new AtomicBoolean();
- private final Promise promise;
private final AtomicInteger sweeps = new AtomicInteger();
+ private final Promise promise;
private final Delegate delegate;
private final HttpChannelOverHTTP channel;
private long idleTimeout;
@@ -119,6 +120,13 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
}
}
+ @Override
+ public ByteBuffer onUpgradeFrom()
+ {
+ HttpReceiverOverHTTP receiver = channel.getHttpReceiver();
+ return receiver.onUpgradeFrom();
+ }
+
public void release()
{
// Restore idle timeout
@@ -171,6 +179,11 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
return true;
}
+ public void remove()
+ {
+ getHttpDestination().remove(this);
+ }
+
@Override
public String toString()
{
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/Predicate.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionUpgrader.java
similarity index 79%
rename from jetty-start/src/main/java/org/eclipse/jetty/start/graph/Predicate.java
rename to jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionUpgrader.java
index b9959323910..c2c43749be7 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/Predicate.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpConnectionUpgrader.java
@@ -16,12 +16,11 @@
// ========================================================================
//
-package org.eclipse.jetty.start.graph;
+package org.eclipse.jetty.client.http;
-/**
- * Matcher of Nodes
- */
-public interface Predicate
+import org.eclipse.jetty.client.HttpResponse;
+
+public interface HttpConnectionUpgrader
{
- public boolean match(Node> input);
+ public void upgrade(HttpResponse response, HttpConnectionOverHTTP connection);
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
index 304ba96d355..284ce08fe5a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java
@@ -22,8 +22,9 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.PoolingHttpDestination;
+import org.eclipse.jetty.client.api.Connection;
-public class HttpDestinationOverHTTP extends PoolingHttpDestination
+public class HttpDestinationOverHTTP extends PoolingHttpDestination
{
public HttpDestinationOverHTTP(HttpClient client, Origin origin)
{
@@ -31,8 +32,8 @@ public class HttpDestinationOverHTTP extends PoolingHttpDestinationA {@link ContentProvider} for form uploads with the {@code "multipart/form-data"}
+ * content type.
+ */
+public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable
+{
+ private static final Logger LOG = Log.getLogger(MultiPartContentProvider.class);
+ private static final byte[] COLON_SPACE_BYTES = new byte[]{':', ' '};
+ private static final byte[] CR_LF_BYTES = new byte[]{'\r', '\n'};
+
+ private final List parts = new ArrayList<>();
+ private final ByteBuffer firstBoundary;
+ private final ByteBuffer middleBoundary;
+ private final ByteBuffer onlyBoundary;
+ private final ByteBuffer lastBoundary;
+ private final AtomicBoolean closed = new AtomicBoolean();
+ private Listener listener;
+ private long length = -1;
+
+ public MultiPartContentProvider()
+ {
+ this(makeBoundary());
+ }
+
+ public MultiPartContentProvider(String boundary)
+ {
+ super("multipart/form-data; boundary=" + boundary);
+ String firstBoundaryLine = "--" + boundary + "\r\n";
+ this.firstBoundary = ByteBuffer.wrap(firstBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ String middleBoundaryLine = "\r\n" + firstBoundaryLine;
+ this.middleBoundary = ByteBuffer.wrap(middleBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ String onlyBoundaryLine = "--" + boundary + "--\r\n";
+ this.onlyBoundary = ByteBuffer.wrap(onlyBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ String lastBoundaryLine = "\r\n" + onlyBoundaryLine;
+ this.lastBoundary = ByteBuffer.wrap(lastBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ }
+
+ private static String makeBoundary()
+ {
+ Random random = new Random();
+ StringBuilder builder = new StringBuilder("JettyHttpClientBoundary");
+ int length = builder.length();
+ while (builder.length() < length + 16)
+ {
+ long rnd = random.nextLong();
+ builder.append(Long.toString(rnd < 0 ? -rnd : rnd, 36));
+ }
+ builder.setLength(length + 16);
+ return builder.toString();
+ }
+
+ /**
+ *
Adds a field part with the given {@code name} as field name, and the given
+ * {@code content} as part content.
+ *
The {@code Content-Type} of this part will be obtained from:
+ *
+ *
the {@code Content-Type} header in the {@code fields} parameter; otherwise
+ *
the {@link org.eclipse.jetty.client.api.ContentProvider.Typed#getContentType()} method if the {@code content} parameter
+ * implements {@link org.eclipse.jetty.client.api.ContentProvider.Typed}; otherwise
+ *
"text/plain"
+ *
+ *
+ * @param name the part name
+ * @param content the part content
+ * @param fields the headers associated with this part
+ */
+ public void addFieldPart(String name, ContentProvider content, HttpFields fields)
+ {
+ addPart(new Part(name, null, "text/plain", content, fields));
+ }
+
+ /**
+ *
Adds a file part with the given {@code name} as field name, the given
+ * {@code fileName} as file name, and the given {@code content} as part content.
+ *
The {@code Content-Type} of this part will be obtained from:
+ *
+ *
the {@code Content-Type} header in the {@code fields} parameter; otherwise
+ *
the {@link org.eclipse.jetty.client.api.ContentProvider.Typed#getContentType()} method if the {@code content} parameter
+ * implements {@link org.eclipse.jetty.client.api.ContentProvider.Typed}; otherwise
+ *
"application/octet-stream"
+ *
+ *
+ * @param name the part name
+ * @param fileName the file name associated to this part
+ * @param content the part content
+ * @param fields the headers associated with this part
+ */
+ public void addFilePart(String name, String fileName, ContentProvider content, HttpFields fields)
+ {
+ addPart(new Part(name, fileName, "application/octet-stream", content, fields));
+ }
+
+ private void addPart(Part part)
+ {
+ parts.add(part);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Added {}", part);
+ }
+
+ @Override
+ public void setListener(Listener listener)
+ {
+ this.listener = listener;
+ if (closed.get())
+ this.length = calculateLength();
+ }
+
+ private long calculateLength()
+ {
+ // Compute the length, if possible.
+ if (parts.isEmpty())
+ {
+ return onlyBoundary.remaining();
+ }
+ else
+ {
+ long result = 0;
+ for (int i = 0; i < parts.size(); ++i)
+ {
+ result += (i == 0) ? firstBoundary.remaining() : middleBoundary.remaining();
+ Part part = parts.get(i);
+ long partLength = part.length;
+ result += partLength;
+ if (partLength < 0)
+ {
+ result = -1;
+ break;
+ }
+ }
+ if (result > 0)
+ result += lastBoundary.remaining();
+ return result;
+ }
+ }
+
+ @Override
+ public long getLength()
+ {
+ return length;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return new MultiPartIterator();
+ }
+
+ @Override
+ public void close()
+ {
+ closed.compareAndSet(false, true);
+ }
+
+ private static class Part
+ {
+ private final String name;
+ private final String fileName;
+ private final String contentType;
+ private final ContentProvider content;
+ private final HttpFields fields;
+ private final ByteBuffer headers;
+ private final long length;
+
+ private Part(String name, String fileName, String contentType, ContentProvider content, HttpFields fields)
+ {
+ this.name = name;
+ this.fileName = fileName;
+ this.contentType = contentType;
+ this.content = content;
+ this.fields = fields;
+ this.headers = headers();
+ this.length = content.getLength() < 0 ? -1 : headers.remaining() + content.getLength();
+ }
+
+ private ByteBuffer headers()
+ {
+ try
+ {
+ // Compute the Content-Disposition.
+ String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\"";
+ if (fileName != null)
+ contentDisposition += "; filename=\"" + fileName + "\"";
+ contentDisposition += "\r\n";
+
+ // Compute the Content-Type.
+ String contentType = fields == null ? null : fields.get(HttpHeader.CONTENT_TYPE);
+ if (contentType == null)
+ {
+ if (content instanceof Typed)
+ contentType = ((Typed)content).getContentType();
+ else
+ contentType = this.contentType;
+ }
+ contentType = "Content-Type: " + contentType + "\r\n";
+
+ if (fields == null || fields.size() == 0)
+ {
+ String headers = contentDisposition;
+ headers += contentType;
+ headers += "\r\n";
+ return ByteBuffer.wrap(headers.getBytes(StandardCharsets.UTF_8));
+ }
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream((fields.size() + 1) * contentDisposition.length());
+ buffer.write(contentDisposition.getBytes(StandardCharsets.UTF_8));
+ buffer.write(contentType.getBytes(StandardCharsets.UTF_8));
+ for (HttpField field : fields)
+ {
+ if (HttpHeader.CONTENT_TYPE.equals(field.getHeader()))
+ continue;
+ buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII));
+ buffer.write(COLON_SPACE_BYTES);
+ buffer.write(field.getValue().getBytes(StandardCharsets.UTF_8));
+ buffer.write(CR_LF_BYTES);
+ }
+ buffer.write(CR_LF_BYTES);
+ return ByteBuffer.wrap(buffer.toByteArray());
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[name=%s,fileName=%s,length=%d,headers=%s]",
+ getClass().getSimpleName(),
+ hashCode(),
+ name,
+ fileName,
+ content.getLength(),
+ fields);
+ }
+ }
+
+ private class MultiPartIterator implements Iterator, Synchronizable, Callback, Closeable
+ {
+ private Iterator iterator;
+ private int index;
+ private State state = State.FIRST_BOUNDARY;
+
+ @Override
+ public boolean hasNext()
+ {
+ return state != State.COMPLETE;
+ }
+
+ @Override
+ public ByteBuffer next()
+ {
+ while (true)
+ {
+ switch (state)
+ {
+ case FIRST_BOUNDARY:
+ {
+ if (parts.isEmpty())
+ {
+ state = State.COMPLETE;
+ return onlyBoundary.slice();
+ }
+ else
+ {
+ state = State.HEADERS;
+ return firstBoundary.slice();
+ }
+ }
+ case HEADERS:
+ {
+ Part part = parts.get(index);
+ ContentProvider content = part.content;
+ if (content instanceof AsyncContentProvider)
+ ((AsyncContentProvider)content).setListener(listener);
+ iterator = content.iterator();
+ state = State.CONTENT;
+ return part.headers.slice();
+ }
+ case CONTENT:
+ {
+ if (iterator.hasNext())
+ return iterator.next();
+ ++index;
+ if (index == parts.size())
+ state = State.LAST_BOUNDARY;
+ else
+ state = State.MIDDLE_BOUNDARY;
+ break;
+ }
+ case MIDDLE_BOUNDARY:
+ {
+ state = State.HEADERS;
+ return middleBoundary.slice();
+ }
+ case LAST_BOUNDARY:
+ {
+ state = State.COMPLETE;
+ return lastBoundary.slice();
+ }
+ case COMPLETE:
+ {
+ throw new NoSuchElementException();
+ }
+ }
+ }
+ }
+
+ @Override
+ public Object getLock()
+ {
+ if (iterator instanceof Synchronizable)
+ return ((Synchronizable)iterator).getLock();
+ return this;
+ }
+
+ @Override
+ public void succeeded()
+ {
+ if (iterator instanceof Callback)
+ ((Callback)iterator).succeeded();
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ if (iterator instanceof Callback)
+ ((Callback)iterator).failed(x);
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ if (iterator instanceof Closeable)
+ ((Closeable)iterator).close();
+ }
+ }
+
+ private enum State
+ {
+ FIRST_BOUNDARY, HEADERS, CONTENT, MIDDLE_BOUNDARY, LAST_BOUNDARY, COMPLETE
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
index 034dfdaee6a..9cea0fd213c 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
@@ -24,7 +24,6 @@ import java.util.Collection;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.TestTracker;
@@ -51,7 +50,7 @@ public abstract class AbstractHttpClientServerTest
protected String scheme;
protected Server server;
protected HttpClient client;
- protected NetworkConnector connector;
+ protected ServerConnector connector;
public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
{
@@ -60,6 +59,12 @@ public abstract class AbstractHttpClientServerTest
}
public void start(Handler handler) throws Exception
+ {
+ startServer(handler);
+ startClient();
+ }
+
+ protected void startServer(Handler handler) throws Exception
{
if (sslContextFactory != null)
{
@@ -80,8 +85,6 @@ public abstract class AbstractHttpClientServerTest
server.addConnector(connector);
server.setHandler(handler);
server.start();
-
- startClient();
}
protected void startClient() throws Exception
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java
new file mode 100644
index 00000000000..4564c5e22fe
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java
@@ -0,0 +1,122 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
+{
+ public ClientConnectionCloseTest(SslContextFactory sslContextFactory)
+ {
+ super(sslContextFactory);
+ }
+
+ @Test
+ public void testClientConnectionCloseShutdownOutputWithoutRequestContent() throws Exception
+ {
+ testClientConnectionCloseShutdownOutput(null);
+ }
+
+ @Test
+ public void testClientConnectionCloseShutdownOutputWithRequestContent() throws Exception
+ {
+ testClientConnectionCloseShutdownOutput(new StringContentProvider("data", StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testClientConnectionCloseShutdownOutputWithChunkedRequestContent() throws Exception
+ {
+ DeferredContentProvider content = new DeferredContentProvider()
+ {
+ @Override
+ public long getLength()
+ {
+ return -1;
+ }
+ };
+ content.offer(ByteBuffer.wrap("data".getBytes(StandardCharsets.UTF_8)));
+ content.close();
+ testClientConnectionCloseShutdownOutput(content);
+ }
+
+ private void testClientConnectionCloseShutdownOutput(ContentProvider content) throws Exception
+ {
+ AtomicReference ref = new AtomicReference<>();
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ ref.set(baseRequest.getHttpChannel().getEndPoint());
+ ServletInputStream input = request.getInputStream();
+ while (true)
+ {
+ int read = input.read();
+ if (read < 0)
+ break;
+ }
+ response.setStatus(HttpStatus.OK_200);
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .path("/ctx/path")
+ .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
+ .content(content)
+ .send();
+
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ // Wait for the FIN to arrive to the server
+ Thread.sleep(1000);
+
+ // Do not read from the server because it will trigger
+ // the send of the TLS Close Message before the response.
+
+ EndPoint serverEndPoint = ref.get();
+ ByteBuffer buffer = BufferUtil.allocate(1);
+ int read = serverEndPoint.fill(buffer);
+ Assert.assertEquals(-1, read);
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
index 8910d648499..dba109cc0a1 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientCustomProxyTest.java
@@ -146,7 +146,7 @@ public class HttpClientCustomProxyTest
}
}
- private class CAFEBABEConnection extends AbstractConnection
+ private class CAFEBABEConnection extends AbstractConnection implements Callback
{
private final ClientConnectionFactory connectionFactory;
private final Map context;
@@ -162,8 +162,19 @@ public class HttpClientCustomProxyTest
public void onOpen()
{
super.onOpen();
+ getEndPoint().write(this, ByteBuffer.wrap(CAFE_BABE));
+ }
+
+ @Override
+ public void succeeded()
+ {
fillInterested();
- getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ close();
}
@Override
@@ -177,7 +188,7 @@ public class HttpClientCustomProxyTest
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
// We are good, upgrade the connection
- ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
+ getEndPoint().upgrade(connectionFactory.newConnection(getEndPoint(), context));
}
catch (Throwable x)
{
@@ -206,7 +217,7 @@ public class HttpClientCustomProxyTest
}
}
- private class CAFEBABEServerConnection extends AbstractConnection
+ private class CAFEBABEServerConnection extends AbstractConnection implements Callback
{
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
@@ -232,15 +243,25 @@ public class HttpClientCustomProxyTest
int filled = getEndPoint().fill(buffer);
Assert.assertEquals(4, filled);
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
- getEndPoint().write(new Callback.Adapter(), buffer);
-
- // We are good, upgrade the connection
- ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
+ getEndPoint().write(this, buffer);
}
catch (Throwable x)
{
close();
}
}
+
+ @Override
+ public void succeeded()
+ {
+ // We are good, upgrade the connection
+ getEndPoint().upgrade(connectionFactory.newConnection(connector, getEndPoint()));
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ close();
+ }
}
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
index 34c0b0ffa9f..dd351546134 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientExplicitConnectionTest.java
@@ -59,7 +59,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
Assert.assertEquals(200, response.getStatus());
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
- ConnectionPool connectionPool = httpDestination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)httpDestination.getConnectionPool();
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
}
@@ -94,7 +94,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
- ConnectionPool connectionPool = httpDestination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)httpDestination.getConnectionPool();
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
index 2584701e5a6..e7ab277f206 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
@@ -25,9 +25,6 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
import org.eclipse.jetty.client.util.DeferredContentProvider;
@@ -89,14 +86,7 @@ public class HttpClientFailureTest
try
{
client.newRequest("localhost", connector.getLocalPort())
- .onRequestHeaders(new Request.HeadersListener()
- {
- @Override
- public void onHeaders(Request request)
- {
- connectionRef.get().getEndPoint().close();
- }
- })
+ .onRequestHeaders(request -> connectionRef.get().getEndPoint().close())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.fail();
@@ -106,7 +96,7 @@ public class HttpClientFailureTest
// Expected.
}
- ConnectionPool connectionPool = connectionRef.get().getHttpDestination().getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)connectionRef.get().getHttpDestination().getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -134,25 +124,17 @@ public class HttpClientFailureTest
final CountDownLatch completeLatch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
client.newRequest("localhost", connector.getLocalPort())
- .onRequestCommit(new Request.CommitListener()
+ .onRequestCommit(request ->
{
- @Override
- public void onCommit(Request request)
- {
- connectionRef.get().getEndPoint().close();
- commitLatch.countDown();
- }
+ connectionRef.get().getEndPoint().close();
+ commitLatch.countDown();
})
.content(content)
.idleTimeout(2, TimeUnit.SECONDS)
- .send(new Response.CompleteListener()
+ .send(result ->
{
- @Override
- public void onComplete(Result result)
- {
- if (result.isFailed())
- completeLatch.countDown();
- }
+ if (result.isFailed())
+ completeLatch.countDown();
});
Assert.assertTrue(commitLatch.await(5, TimeUnit.SECONDS));
@@ -170,7 +152,7 @@ public class HttpClientFailureTest
Assert.assertTrue(contentLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
- ConnectionPool connectionPool = connectionRef.get().getHttpDestination().getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)connectionRef.get().getHttpDestination().getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index 662b32a34e0..404a3e8e3f6 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -47,6 +47,7 @@ import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -63,6 +64,7 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.BufferingResponseListener;
@@ -75,6 +77,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.TestingDir;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
@@ -111,7 +114,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertEquals(200, response.getStatus());
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
long start = System.nanoTime();
HttpConnectionOverHTTP connection = null;
@@ -367,16 +370,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final byte[] content = {0, 1, 2, 3};
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
- .onRequestContent(new Request.ContentListener()
+ .onRequestContent((request, buffer) ->
{
- @Override
- public void onContent(Request request, ByteBuffer buffer)
- {
- byte[] bytes = new byte[buffer.remaining()];
- buffer.get(bytes);
- if (!Arrays.equals(content, bytes))
- request.abort(new Exception());
- }
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ if (!Arrays.equals(content, bytes))
+ request.abort(new Exception());
})
.content(new BytesContentProvider(content))
.timeout(5, TimeUnit.SECONDS)
@@ -401,16 +400,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final AtomicInteger progress = new AtomicInteger();
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
- .onRequestContent(new Request.ContentListener()
+ .onRequestContent((request, buffer) ->
{
- @Override
- public void onContent(Request request, ByteBuffer buffer)
- {
- byte[] bytes = new byte[buffer.remaining()];
- Assert.assertEquals(1, bytes.length);
- buffer.get(bytes);
- Assert.assertEquals(bytes[0], progress.getAndIncrement());
- }
+ byte[] bytes = new byte[buffer.remaining()];
+ Assert.assertEquals(1, bytes.length);
+ buffer.get(bytes);
+ Assert.assertEquals(bytes[0], progress.getAndIncrement());
})
.content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
.timeout(5, TimeUnit.SECONDS)
@@ -432,19 +427,15 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final CountDownLatch successLatch = new CountDownLatch(2);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .onRequestBegin(new Request.BeginListener()
+ .onRequestBegin(request ->
{
- @Override
- public void onBegin(Request request)
+ try
{
- try
- {
- latch.await();
- }
- catch (InterruptedException x)
- {
- x.printStackTrace();
- }
+ latch.await();
+ }
+ catch (InterruptedException x)
+ {
+ x.printStackTrace();
}
})
.send(new Response.Listener.Adapter()
@@ -459,14 +450,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .onRequestQueued(new Request.QueuedListener()
- {
- @Override
- public void onQueued(Request request)
- {
- latch.countDown();
- }
- })
+ .onRequestQueued(request -> latch.countDown())
.send(new Response.Listener.Adapter()
{
@Override
@@ -514,27 +498,16 @@ public class HttpClientTest extends AbstractHttpClientServerTest
latch.countDown();
}
})
- .onResponseFailure(new Response.FailureListener()
- {
- @Override
- public void onFailure(Response response, Throwable failure)
- {
- latch.countDown();
- }
- })
+ .onResponseFailure((response, failure) -> latch.countDown())
.send(null);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/two")
- .onResponseSuccess(new Response.SuccessListener()
+ .onResponseSuccess(response ->
{
- @Override
- public void onSuccess(Response response)
- {
- Assert.assertEquals(200, response.getStatus());
- latch.countDown();
- }
+ Assert.assertEquals(200, response.getStatus());
+ latch.countDown();
})
.send(null);
@@ -564,14 +537,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.file(file)
- .onRequestSuccess(new Request.SuccessListener()
+ .onRequestSuccess(request ->
{
- @Override
- public void onSuccess(Request request)
- {
- requestTime.set(System.nanoTime());
- latch.countDown();
- }
+ requestTime.set(System.nanoTime());
+ latch.countDown();
})
.send(new Response.Listener.Adapter()
{
@@ -674,14 +643,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final int port = connector.getLocalPort();
client.newRequest(host, port)
.scheme(scheme)
- .onRequestBegin(new Request.BeginListener()
+ .onRequestBegin(request ->
{
- @Override
- public void onBegin(Request request)
- {
- HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- destination.getConnectionPool().getActiveConnections().peek().close();
- }
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ connectionPool.getActiveConnections().iterator().next().close();
})
.send(new Response.Listener.Adapter()
{
@@ -773,14 +739,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .onResponseHeader(new Response.HeaderListener()
- {
- @Override
- public boolean onHeader(Response response, HttpField field)
- {
- return !field.getName().equals(headerName);
- }
- })
+ .onResponseHeader((response1, field) -> !field.getName().equals(headerName))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -864,16 +823,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("idontexist", 80)
- .send(new Response.CompleteListener()
+ .send(result ->
{
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- Throwable failure = result.getFailure();
- Assert.assertTrue(failure instanceof UnknownHostException);
- latch.countDown();
- }
+ Assert.assertTrue(result.isFailed());
+ Throwable failure = result.getFailure();
+ Assert.assertTrue(failure instanceof UnknownHostException);
+ latch.countDown();
});
Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
}
@@ -1323,14 +1278,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
final CountDownLatch completeLatch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .send(new Response.CompleteListener()
+ .send(result ->
{
- @Override
- public void onComplete(Result result)
- {
- if (result.isFailed())
- completeLatch.countDown();
- }
+ if (result.isFailed())
+ completeLatch.countDown();
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
@@ -1485,6 +1436,54 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
}
+ @Test
+ public void testRequestSentOnlyAfterConnectionOpen() throws Exception
+ {
+ startServer(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ }
+ });
+
+ final AtomicBoolean open = new AtomicBoolean();
+ client = new HttpClient(new HttpClientTransportOverHTTP()
+ {
+ @Override
+ protected HttpConnectionOverHTTP newHttpConnection(EndPoint endPoint, HttpDestination destination, Promise promise)
+ {
+ return new HttpConnectionOverHTTP(endPoint, destination, promise)
+ {
+ @Override
+ public void onOpen()
+ {
+ open.set(true);
+ super.onOpen();
+ }
+ };
+ }
+ }, sslContextFactory);
+ client.start();
+
+ final CountDownLatch latch = new CountDownLatch(2);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .onRequestBegin(request ->
+ {
+ Assert.assertTrue(open.get());
+ latch.countDown();
+ })
+ .send(result ->
+ {
+ if (result.isSucceeded())
+ latch.countDown();
+ });
+
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
@Test
public void testCONNECTWithHTTP10() throws Exception
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
index e41ee143d36..9ed138fa11b 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
@@ -448,7 +448,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
start(new EmptyServerHandler());
long timeout = 1000;
- Request request = client.newRequest("badscheme://localhost:" + connector.getLocalPort());
+ Request request = client.newRequest("badscheme://localhost:badport");
try
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java
index 61e8cf53d8e..bc80ff7aaea 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdown.java
@@ -31,8 +31,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpChannelOverHTTP;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
@@ -121,14 +119,7 @@ public class HttpClientUploadDuringServerShutdown
int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024);
client.newRequest("localhost", 8888)
.content(new BytesContentProvider(new byte[length]))
- .send(new Response.CompleteListener()
- {
- @Override
- public void onComplete(Result result)
- {
- latch.countDown();
- }
- });
+ .send(result -> latch.countDown());
long sleep = 1 + random.nextInt(10);
TimeUnit.MILLISECONDS.sleep(sleep);
}
@@ -244,35 +235,24 @@ public class HttpClientUploadDuringServerShutdown
final CountDownLatch completeLatch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.timeout(10, TimeUnit.SECONDS)
- .onRequestBegin(new org.eclipse.jetty.client.api.Request.BeginListener()
+ .onRequestBegin(request ->
{
- @Override
- public void onBegin(org.eclipse.jetty.client.api.Request request)
+ try
{
- try
- {
- beginLatch.countDown();
- completeLatch.await(5, TimeUnit.SECONDS);
- }
- catch (InterruptedException x)
- {
- x.printStackTrace();
- }
+ beginLatch.countDown();
+ completeLatch.await(5, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException x)
+ {
+ x.printStackTrace();
}
})
- .send(new Response.CompleteListener()
- {
- @Override
- public void onComplete(Result result)
- {
- completeLatch.countDown();
- }
- });
+ .send(result -> completeLatch.countDown());
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", connector.getLocalPort());
- ConnectionPool pool = destination.getConnectionPool();
+ DuplexConnectionPool pool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, pool.getConnectionCount());
Assert.assertEquals(0, pool.getIdleConnections().size());
Assert.assertEquals(0, pool.getActiveConnections().size());
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index 771f2368dfc..684ff02dce7 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -69,35 +70,24 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(3);
client.newRequest(host, port)
.scheme(scheme)
- .onRequestSuccess(new Request.SuccessListener()
+ .onRequestSuccess(request -> successLatch.countDown())
+ .onResponseHeaders(response ->
{
- @Override
- public void onSuccess(Request request)
- {
- successLatch.countDown();
- }
- })
- .onResponseHeaders(new Response.HeadersListener()
- {
- @Override
- public void onHeaders(Response response)
- {
- Assert.assertEquals(0, idleConnections.size());
- Assert.assertEquals(1, activeConnections.size());
- headersLatch.countDown();
- }
+ Assert.assertEquals(0, idleConnections.size());
+ Assert.assertEquals(1, activeConnections.size());
+ headersLatch.countDown();
})
.send(new Response.Listener.Adapter()
{
@@ -130,12 +120,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch beginLatch = new CountDownLatch(1);
@@ -145,7 +135,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
@Override
public void onBegin(Request request)
{
- activeConnections.peek().close();
+ activeConnections.iterator().next().close();
beginLatch.countDown();
}
@@ -181,12 +171,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
final Queue idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch successLatch = new CountDownLatch(3);
@@ -241,12 +231,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final long delay = 1000;
@@ -314,12 +304,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
server.stop();
@@ -327,22 +317,11 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
final CountDownLatch failureLatch = new CountDownLatch(2);
client.newRequest(host, port)
.scheme(scheme)
- .onRequestFailure(new Request.FailureListener()
+ .onRequestFailure((request, failure) -> failureLatch.countDown())
+ .send(result ->
{
- @Override
- public void onFailure(Request request, Throwable failure)
- {
- failureLatch.countDown();
- }
- })
- .send(new Response.Listener.Adapter()
- {
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- failureLatch.countDown();
- }
+ Assert.assertTrue(result.isFailed());
+ failureLatch.countDown();
});
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
@@ -367,12 +346,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final CountDownLatch latch = new CountDownLatch(1);
@@ -417,12 +396,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
@@ -467,12 +446,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
ContentResponse response = client.newRequest(host, port)
@@ -499,25 +478,21 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
- final Queue idleConnections = connectionPool.getIdleConnections();
+ final Collection idleConnections = connectionPool.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
- final Queue activeConnections = connectionPool.getActiveConnections();
+ final Collection activeConnections = connectionPool.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
client.setStrictEventOrdering(false);
ContentResponse response = client.newRequest(host, port)
.scheme(scheme)
- .onResponseBegin(new Response.BeginListener()
+ .onResponseBegin(response1 ->
{
- @Override
- public void onBegin(Response response)
- {
- // Simulate a HTTP 1.0 response has been received.
- ((HttpResponse)response).version(HttpVersion.HTTP_1_0);
- }
+ // Simulate a HTTP 1.0 response has been received.
+ ((HttpResponse)response1).version(HttpVersion.HTTP_1_0);
})
.send();
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 c4d6010c515..4b33e574e19 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
@@ -25,12 +25,12 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
@@ -88,7 +88,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -135,7 +135,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -182,7 +182,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -204,14 +204,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .onRequestCommit(new Request.CommitListener()
+ .onRequestCommit(request ->
{
- @Override
- public void onCommit(Request request)
- {
- aborted.set(request.abort(cause));
- latch.countDown();
- }
+ aborted.set(request.abort(cause));
+ latch.countDown();
})
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -225,7 +221,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -260,14 +256,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .onRequestCommit(new Request.CommitListener()
+ .onRequestCommit(request ->
{
- @Override
- public void onCommit(Request request)
- {
- aborted.set(request.abort(cause));
- latch.countDown();
- }
+ aborted.set(request.abort(cause));
+ latch.countDown();
})
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
{
@@ -289,7 +281,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -315,14 +307,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
- .onRequestContent(new Request.ContentListener()
+ .onRequestContent((request, content) ->
{
- @Override
- public void onContent(Request request, ByteBuffer content)
- {
- aborted.set(request.abort(cause));
- latch.countDown();
- }
+ aborted.set(request.abort(cause));
+ latch.countDown();
})
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
{
@@ -344,7 +332,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -454,7 +442,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
- ConnectionPool connectionPool = destination.getConnectionPool();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Assert.assertEquals(0, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
@@ -486,15 +474,11 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(3 * delay, TimeUnit.MILLISECONDS);
- request.send(new Response.CompleteListener()
+ request.send(result ->
{
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- Assert.assertSame(cause, result.getFailure());
- latch.countDown();
- }
+ Assert.assertTrue(result.isFailed());
+ Assert.assertSame(cause, result.getFailure());
+ latch.countDown();
});
TimeUnit.MILLISECONDS.sleep(delay);
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java
new file mode 100644
index 00000000000..47a7760e1a7
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ServerConnectionCloseTest.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ServerConnectionCloseTest
+{
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+ private HttpClient client;
+
+ private void startClient() throws Exception
+ {
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ clientThreads.setName("client");
+ client = new HttpClient(new HttpClientTransportOverHTTP(1), null);
+ client.setExecutor(clientThreads);
+ client.start();
+ }
+
+ @After
+ public void dispose() throws Exception
+ {
+ if (client != null)
+ client.stop();
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithoutContent() throws Exception
+ {
+ testServerSendsConnectionClose(true, false, "");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithContent() throws Exception
+ {
+ testServerSendsConnectionClose(true, false, "data");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithChunkedContent() throws Exception
+ {
+ testServerSendsConnectionClose(true, true, "data");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithoutContentButDoesNotClose() throws Exception
+ {
+ testServerSendsConnectionClose(false, false, "");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithContentButDoesNotClose() throws Exception
+ {
+ testServerSendsConnectionClose(false, false, "data");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithChunkedContentButDoesNotClose() throws Exception
+ {
+ testServerSendsConnectionClose(false, true, "data");
+ }
+
+ private void testServerSendsConnectionClose(boolean shutdownOutput, boolean chunked, String content) throws Exception
+ {
+ ServerSocket server = new ServerSocket(0);
+ int port = server.getLocalPort();
+
+ startClient();
+
+ Request request = client.newRequest("localhost", port).path("/ctx/path");
+ FutureResponseListener listener = new FutureResponseListener(request);
+ request.send(listener);
+
+ Socket socket = server.accept();
+
+ InputStream input = socket.getInputStream();
+ consumeRequest(input);
+
+ OutputStream output = socket.getOutputStream();
+ String serverResponse = "" +
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n";
+ if (chunked)
+ {
+ serverResponse += "" +
+ "Transfer-Encoding: chunked\r\n" +
+ "\r\n";
+ for (int i = 0; i < 2; ++i)
+ {
+ serverResponse +=
+ Integer.toHexString(content.length()) + "\r\n" +
+ content + "\r\n";
+ }
+ serverResponse += "" +
+ "0\r\n" +
+ "\r\n";
+ }
+ else
+ {
+ serverResponse += "Content-Length: " + content.length() + "\r\n";
+ serverResponse += "\r\n";
+ serverResponse += content;
+ }
+
+ output.write(serverResponse.getBytes("UTF-8"));
+ output.flush();
+ if (shutdownOutput)
+ socket.shutdownOutput();
+
+ ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ // Give some time to process the connection.
+ Thread.sleep(1000);
+
+ // Connection should have been removed from pool.
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Assert.assertEquals(0, connectionPool.getConnectionCount());
+ Assert.assertEquals(0, connectionPool.getIdleConnectionCount());
+ Assert.assertEquals(0, connectionPool.getActiveConnectionCount());
+ }
+
+ private boolean consumeRequest(InputStream input) throws IOException
+ {
+ int crlfs = 0;
+ while (true)
+ {
+ int read = input.read();
+ if (read < 0)
+ return true;
+ if (read == '\r' || read == '\n')
+ ++crlfs;
+ else
+ crlfs = 0;
+ if (crlfs == 4)
+ return false;
+ }
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java
new file mode 100644
index 00000000000..d48ed223149
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/TLSServerConnectionCloseTest.java
@@ -0,0 +1,213 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
+import org.eclipse.jetty.client.util.FutureResponseListener;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.toolchain.test.TestTracker;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TLSServerConnectionCloseTest
+{
+ @Parameterized.Parameters(name = "CloseMode: {0}")
+ public static Object[] parameters()
+ {
+ return new Object[]{CloseMode.NONE, CloseMode.CLOSE, CloseMode.ABRUPT};
+ }
+
+ @Rule
+ public final TestTracker tracker = new TestTracker();
+ private HttpClient client;
+ private final CloseMode closeMode;
+
+ public TLSServerConnectionCloseTest(CloseMode closeMode)
+ {
+ this.closeMode = closeMode;
+ }
+
+ private void startClient() throws Exception
+ {
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.setEndpointIdentificationAlgorithm("");
+ sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
+ sslContextFactory.setKeyStorePassword("storepwd");
+ sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
+ sslContextFactory.setTrustStorePassword("storepwd");
+
+ QueuedThreadPool clientThreads = new QueuedThreadPool();
+ clientThreads.setName("client");
+ client = new HttpClient(new HttpClientTransportOverHTTP(1), sslContextFactory);
+ client.setExecutor(clientThreads);
+ client.start();
+ }
+
+ @After
+ public void dispose() throws Exception
+ {
+ if (client != null)
+ client.stop();
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithoutContent() throws Exception
+ {
+ testServerSendsConnectionClose(false, "");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithContent() throws Exception
+ {
+ testServerSendsConnectionClose(false, "data");
+ }
+
+ @Test
+ public void testServerSendsConnectionCloseWithChunkedContent() throws Exception
+ {
+ testServerSendsConnectionClose(true, "data");
+ }
+
+ private void testServerSendsConnectionClose(boolean chunked, String content) throws Exception
+ {
+ ServerSocket server = new ServerSocket(0);
+ int port = server.getLocalPort();
+
+ startClient();
+
+ Request request = client.newRequest("localhost", port).scheme("https").path("/ctx/path");
+ FutureResponseListener listener = new FutureResponseListener(request);
+ request.send(listener);
+
+ Socket socket = server.accept();
+ SSLContext sslContext = client.getSslContextFactory().getSslContext();
+ SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, "localhost", port, false);
+ sslSocket.setUseClientMode(false);
+ sslSocket.startHandshake();
+
+ InputStream input = sslSocket.getInputStream();
+ consumeRequest(input);
+
+ OutputStream output = sslSocket.getOutputStream();
+ String serverResponse = "" +
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n";
+ if (chunked)
+ {
+ serverResponse += "" +
+ "Transfer-Encoding: chunked\r\n" +
+ "\r\n";
+ for (int i = 0; i < 2; ++i)
+ {
+ serverResponse +=
+ Integer.toHexString(content.length()) + "\r\n" +
+ content + "\r\n";
+ }
+ serverResponse += "" +
+ "0\r\n" +
+ "\r\n";
+ }
+ else
+ {
+ serverResponse += "Content-Length: " + content.length() + "\r\n";
+ serverResponse += "\r\n";
+ serverResponse += content;
+ }
+
+ output.write(serverResponse.getBytes("UTF-8"));
+ output.flush();
+
+ switch (closeMode)
+ {
+ case NONE:
+ {
+ break;
+ }
+ case CLOSE:
+ {
+ sslSocket.close();
+ break;
+ }
+ case ABRUPT:
+ {
+ socket.shutdownOutput();
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException();
+ }
+ }
+
+ ContentResponse response = listener.get(5, TimeUnit.SECONDS);
+ Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ // Give some time to process the connection.
+ Thread.sleep(1000);
+
+ // Connection should have been removed from pool.
+ HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Assert.assertEquals(0, connectionPool.getConnectionCount());
+ Assert.assertEquals(0, connectionPool.getIdleConnectionCount());
+ Assert.assertEquals(0, connectionPool.getActiveConnectionCount());
+ }
+
+ private boolean consumeRequest(InputStream input) throws IOException
+ {
+ int crlfs = 0;
+ while (true)
+ {
+ int read = input.read();
+ if (read < 0)
+ return true;
+ if (read == '\r' || read == '\n')
+ ++crlfs;
+ else
+ crlfs = 0;
+ if (crlfs == 4)
+ return false;
+ }
+ }
+
+ private enum CloseMode
+ {
+ NONE, CLOSE, ABRUPT
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java
index d6b707f0d56..a9ad8174fcb 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ValidatingConnectionPoolTest.java
@@ -194,7 +194,7 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest
return new HttpDestinationOverHTTP(getHttpClient(), origin)
{
@Override
- protected ConnectionPool newConnectionPool(HttpClient client)
+ protected DuplexConnectionPool newConnectionPool(HttpClient client)
{
return new ValidatingConnectionPool(this, client.getMaxConnectionsPerDestination(), this, client.getScheduler(), timeout);
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
index 4b6075b5aa3..bf9af834d94 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java
@@ -25,15 +25,13 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
import org.eclipse.jetty.client.ConnectionPool;
+import org.eclipse.jetty.client.DuplexConnectionPool;
import org.eclipse.jetty.client.EmptyServerHandler;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -59,11 +57,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
public void test_FirstAcquire_WithEmptyQueue() throws Exception
{
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
- Connection connection = destination.acquire();
+ destination.start();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Connection connection = connectionPool.acquire();
if (connection == null)
{
// There are no queued requests, so the newly created connection will be idle
- connection = timedPoll(destination.getConnectionPool().getIdleConnections(), 5, TimeUnit.SECONDS);
+ connection = timedPoll(connectionPool.getIdleConnections(), 5, TimeUnit.SECONDS);
}
Assert.assertNotNull(connection);
}
@@ -72,7 +72,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
{
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
- Connection connection1 = destination.acquire();
+ destination.start();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Connection connection1 = connectionPool.acquire();
if (connection1 == null)
{
// There are no queued requests, so the newly created connection will be idle
@@ -80,11 +82,11 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
{
TimeUnit.MILLISECONDS.sleep(50);
- connection1 = destination.getConnectionPool().getIdleConnections().peek();
+ connection1 = connectionPool.getIdleConnections().peek();
}
Assert.assertNotNull(connection1);
- Connection connection2 = destination.acquire();
+ Connection connection2 = connectionPool.acquire();
Assert.assertSame(connection1, connection2);
}
}
@@ -99,16 +101,16 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
@Override
protected ConnectionPool newConnectionPool(HttpClient client)
{
- return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
+ return new DuplexConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
{
@Override
- protected void idleCreated(Connection connection)
+ protected void onCreated(Connection connection)
{
try
{
idleLatch.countDown();
latch.await(5, TimeUnit.SECONDS);
- super.idleCreated(connection);
+ super.onCreated(connection);
}
catch (InterruptedException x)
{
@@ -118,7 +120,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
};
}
};
- Connection connection1 = destination.acquire();
+ destination.start();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Connection connection1 = connectionPool.acquire();
// Make sure we entered idleCreated().
Assert.assertTrue(idleLatch.await(5, TimeUnit.SECONDS));
@@ -128,13 +132,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
Assert.assertNull(connection1);
// Second attempt also returns null because we delayed idleCreated() above.
- Connection connection2 = destination.acquire();
+ Connection connection2 = connectionPool.acquire();
Assert.assertNull(connection2);
latch.countDown();
// There must be 2 idle connections.
- Queue idleConnections = destination.getConnectionPool().getIdleConnections();
+ Queue idleConnections = connectionPool.getIdleConnections();
Connection connection = timedPoll(idleConnections, 5, TimeUnit.SECONDS);
Assert.assertNotNull(connection);
connection = timedPoll(idleConnections, 5, TimeUnit.SECONDS);
@@ -145,23 +149,25 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
{
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
- HttpConnectionOverHTTP connection1 = destination.acquire();
+ destination.start();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ HttpConnectionOverHTTP connection1 = (HttpConnectionOverHTTP)connectionPool.acquire();
long start = System.nanoTime();
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
{
TimeUnit.MILLISECONDS.sleep(50);
- connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
+ connection1 = (HttpConnectionOverHTTP)connectionPool.getIdleConnections().peek();
}
Assert.assertNotNull(connection1);
// Acquire the connection to make it active
- Assert.assertSame(connection1, destination.acquire());
+ Assert.assertSame(connection1, connectionPool.acquire());
destination.process(connection1);
destination.release(connection1);
- Connection connection2 = destination.acquire();
+ Connection connection2 = connectionPool.acquire();
Assert.assertSame(connection1, connection2);
}
@@ -172,7 +178,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
client.setIdleTimeout(idleTimeout);
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
- Connection connection1 = destination.acquire();
+ destination.start();
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Connection connection1 = connectionPool.acquire();
if (connection1 == null)
{
// There are no queued requests, so the newly created connection will be idle
@@ -180,13 +188,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
{
TimeUnit.MILLISECONDS.sleep(50);
- connection1 = destination.getConnectionPool().getIdleConnections().peek();
+ connection1 = connectionPool.getIdleConnections().peek();
}
Assert.assertNotNull(connection1);
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
- connection1 = destination.getConnectionPool().getIdleConnections().poll();
+ connection1 = connectionPool.getIdleConnections().poll();
Assert.assertNull(connection1);
}
}
@@ -210,35 +218,23 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/one")
- .onRequestQueued(new Request.QueuedListener()
+ .onRequestQueued(request ->
{
- @Override
- public void onQueued(Request request)
- {
- // This request exceeds the maximum queued, should fail
- client.newRequest("localhost", connector.getLocalPort())
- .scheme(scheme)
- .path("/two")
- .send(new Response.CompleteListener()
- {
- @Override
- public void onComplete(Result result)
- {
- Assert.assertTrue(result.isFailed());
- Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
- failureLatch.countDown();
- }
- });
- }
+ // This request exceeds the maximum queued, should fail
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .path("/two")
+ .send(result ->
+ {
+ Assert.assertTrue(result.isFailed());
+ Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
+ failureLatch.countDown();
+ });
})
- .send(new Response.CompleteListener()
+ .send(result ->
{
- @Override
- public void onComplete(Result result)
- {
- if (result.isSucceeded())
- successLatch.countDown();
- }
+ if (result.isSucceeded())
+ successLatch.countDown();
});
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
index 55217609b57..feb5cf0c58a 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
@@ -61,8 +61,10 @@ public class HttpReceiverOverHTTPTest
client = new HttpClient();
client.start();
destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
endPoint = new ByteArrayEndPoint();
- connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
+ connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<>());
+ endPoint.setConnection(connection);
}
@After
@@ -207,7 +209,7 @@ public class HttpReceiverOverHTTPTest
@Test
public void test_FillInterested_RacingWith_BufferRelease() throws Exception
{
- connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter())
+ connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<>())
{
@Override
protected HttpChannelOverHTTP newHttpChannel()
@@ -234,6 +236,7 @@ public class HttpReceiverOverHTTPTest
};
}
};
+ endPoint.setConnection(connection);
// Partial response to trigger the call to fillInterested().
endPoint.addInput("" +
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
index b98aea13ab1..e592c42ca8f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
@@ -67,6 +67,7 @@ public class HttpSenderOverHTTPTest
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
final CountDownLatch headersLatch = new CountDownLatch(1);
@@ -100,6 +101,7 @@ public class HttpSenderOverHTTPTest
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
connection.send(request, null);
@@ -129,6 +131,7 @@ public class HttpSenderOverHTTPTest
// Shutdown output to trigger the exception on write
endPoint.shutdownOutput();
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
final CountDownLatch failureLatch = new CountDownLatch(2);
@@ -158,6 +161,7 @@ public class HttpSenderOverHTTPTest
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
final CountDownLatch failureLatch = new CountDownLatch(2);
@@ -193,6 +197,7 @@ public class HttpSenderOverHTTPTest
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
String content = "abcdef";
@@ -227,6 +232,7 @@ public class HttpSenderOverHTTPTest
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
String content1 = "0123456789";
@@ -262,6 +268,7 @@ public class HttpSenderOverHTTPTest
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
+ destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
String content1 = "0123456789";
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
index 8dd287f4921..535f7ecd898 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -54,10 +54,10 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
-import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnection;
@@ -173,9 +173,9 @@ public class SslBytesServerTest extends SslBytesTest
ServerConnector connector = new ServerConnector(server, null,null,null,1,1,sslFactory, httpFactory)
{
@Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
- SelectChannelEndPoint endp = super.newEndPoint(channel,selectSet,key);
+ ChannelEndPoint endp = super.newEndPoint(channel,selectSet,key);
serverEndPoint.set(endp);
return endp;
}
@@ -367,11 +367,19 @@ public class SslBytesServerTest extends SslBytesTest
System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
proxy.flushToServer(0, chunk);
+
// Close the raw socket
proxy.flushToServer(null);
// Expect the server to send a FIN as well
record = proxy.readFromServer();
+ if (record!=null)
+ {
+ // Close alert snuck out // TODO check if this is acceptable
+ Assert.assertEquals(Type.ALERT,record.getType());
+ record = proxy.readFromServer();
+ }
+
Assert.assertNull(record);
// Check that we did not spin
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java
new file mode 100644
index 00000000000..06c89e28e8e
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java
@@ -0,0 +1,448 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.client.util;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.client.AbstractHttpClientServerTest;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
+{
+ public MultiPartContentProviderTest(SslContextFactory sslContextFactory)
+ {
+ super(sslContextFactory);
+ }
+
+ @Test
+ public void testEmptyMultiPart() throws Exception
+ {
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ Collection parts = request.getParts();
+ Assert.assertEquals(0, parts.size());
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ multiPart.close();
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testSimpleField() throws Exception
+ {
+ String name = "field";
+ String value = "value";
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ Collection parts = request.getParts();
+ Assert.assertEquals(1, parts.size());
+ Part part = parts.iterator().next();
+ Assert.assertEquals(name, part.getName());
+ Assert.assertEquals(value, IO.toString(part.getInputStream()));
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ multiPart.addFieldPart(name, new StringContentProvider(value), null);
+ multiPart.close();
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testFieldWithOverridenContentType() throws Exception
+ {
+ String name = "field";
+ String value = "\u00e8";
+ Charset encoding = StandardCharsets.ISO_8859_1;
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ Collection parts = request.getParts();
+ Assert.assertEquals(1, parts.size());
+ Part part = parts.iterator().next();
+ Assert.assertEquals(name, part.getName());
+ String contentType = part.getContentType();
+ Assert.assertNotNull(contentType);
+ int equal = contentType.lastIndexOf('=');
+ Charset charset = Charset.forName(contentType.substring(equal + 1));
+ Assert.assertEquals(encoding, charset);
+ Assert.assertEquals(value, IO.toString(part.getInputStream(), charset));
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ HttpFields fields = new HttpFields();
+ fields.put(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + encoding.name());
+ BytesContentProvider content = new BytesContentProvider(value.getBytes(encoding));
+ multiPart.addFieldPart(name, content, fields);
+ multiPart.close();
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testFieldDeferred() throws Exception
+ {
+ String name = "field";
+ byte[] data = "Hello, World".getBytes(StandardCharsets.US_ASCII);
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ Collection parts = request.getParts();
+ Assert.assertEquals(1, parts.size());
+ Part part = parts.iterator().next();
+ Assert.assertEquals(name, part.getName());
+ Assert.assertEquals("text/plain", part.getContentType());
+ Assert.assertArrayEquals(data, IO.readBytes(part.getInputStream()));
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ DeferredContentProvider content = new DeferredContentProvider();
+ multiPart.addFieldPart(name, content, null);
+ multiPart.close();
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send(result ->
+ {
+ if (result.isSucceeded())
+ {
+ Assert.assertEquals(200, result.getResponse().getStatus());
+ responseLatch.countDown();
+ }
+ });
+
+ // Wait until the request has been sent.
+ Thread.sleep(1000);
+
+ // Provide the content.
+ content.offer(ByteBuffer.wrap(data));
+ content.close();
+
+ Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testFileFromInputStream() throws Exception
+ {
+ String name = "file";
+ String fileName = "upload.png";
+ String contentType = "image/png";
+ byte[] data = new byte[512];
+ new Random().nextBytes(data);
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ Collection parts = request.getParts();
+ Assert.assertEquals(1, parts.size());
+ Part part = parts.iterator().next();
+ Assert.assertEquals(name, part.getName());
+ Assert.assertEquals(contentType, part.getContentType());
+ Assert.assertEquals(fileName, part.getSubmittedFileName());
+ Assert.assertEquals(data.length, part.getSize());
+ Assert.assertArrayEquals(data, IO.readBytes(part.getInputStream()));
+ }
+ });
+
+ CountDownLatch closeLatch = new CountDownLatch(1);
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ InputStreamContentProvider content = new InputStreamContentProvider(new ByteArrayInputStream(data)
+ {
+ @Override
+ public void close() throws IOException
+ {
+ super.close();
+ closeLatch.countDown();
+ }
+ });
+ HttpFields fields = new HttpFields();
+ fields.put(HttpHeader.CONTENT_TYPE, contentType);
+ multiPart.addFilePart(name, fileName, content, fields);
+ multiPart.close();
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send();
+
+ Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertEquals(200, response.getStatus());
+ }
+
+ @Test
+ public void testFileFromPath() throws Exception
+ {
+ // Prepare a file to upload.
+ String data = "multipart_test_\u20ac";
+ Path tmpDir = MavenTestingUtils.getTargetTestingPath();
+ Path tmpPath = Files.createTempFile(tmpDir, "multipart_", ".txt");
+ Charset encoding = StandardCharsets.UTF_8;
+ try (BufferedWriter writer = Files.newBufferedWriter(tmpPath, encoding, StandardOpenOption.CREATE))
+ {
+ writer.write(data);
+ }
+
+ String name = "file";
+ String contentType = "text/plain; charset=" + encoding.name();
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ Collection parts = request.getParts();
+ Assert.assertEquals(1, parts.size());
+ Part part = parts.iterator().next();
+ Assert.assertEquals(name, part.getName());
+ Assert.assertEquals(contentType, part.getContentType());
+ Assert.assertEquals(tmpPath.getFileName().toString(), part.getSubmittedFileName());
+ Assert.assertEquals(Files.size(tmpPath), part.getSize());
+ Assert.assertEquals(data, IO.toString(part.getInputStream(), encoding));
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ ContentProvider content = new PathContentProvider(contentType, tmpPath);
+ multiPart.addFilePart(name, tmpPath.getFileName().toString(), content, null);
+ multiPart.close();
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+
+ Files.delete(tmpPath);
+ }
+
+ @Test
+ public void testFieldWithFile() throws Exception
+ {
+ // Prepare a file to upload.
+ byte[] data = new byte[1024];
+ new Random().nextBytes(data);
+ Path tmpDir = MavenTestingUtils.getTargetTestingPath();
+ Path tmpPath = Files.createTempFile(tmpDir, "multipart_", ".txt");
+ try (OutputStream output = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE))
+ {
+ output.write(data);
+ }
+
+ String field = "field";
+ String value = "\u20ac";
+ String fileField = "file";
+ Charset encoding = StandardCharsets.UTF_8;
+ String contentType = "text/plain;charset=" + encoding.name();
+ String headerName = "foo";
+ String headerValue = "bar";
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ List parts = new ArrayList<>(request.getParts());
+ Assert.assertEquals(2, parts.size());
+ Part fieldPart = parts.get(0);
+ Part filePart = parts.get(1);
+ if (!field.equals(fieldPart.getName()))
+ {
+ Part swap = filePart;
+ filePart = fieldPart;
+ fieldPart = swap;
+ }
+
+ Assert.assertEquals(field, fieldPart.getName());
+ Assert.assertEquals(contentType, fieldPart.getContentType());
+ Assert.assertEquals(value, IO.toString(fieldPart.getInputStream(), encoding));
+ Assert.assertEquals(headerValue, fieldPart.getHeader(headerName));
+
+ Assert.assertEquals(fileField, filePart.getName());
+ Assert.assertEquals("application/octet-stream", filePart.getContentType());
+ Assert.assertEquals(tmpPath.getFileName().toString(), filePart.getSubmittedFileName());
+ Assert.assertEquals(Files.size(tmpPath), filePart.getSize());
+ Assert.assertArrayEquals(data, IO.readBytes(filePart.getInputStream()));
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ HttpFields fields = new HttpFields();
+ fields.put(headerName, headerValue);
+ multiPart.addFieldPart(field, new StringContentProvider(value, encoding), fields);
+ multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathContentProvider(tmpPath), null);
+ multiPart.close();
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send();
+
+ Assert.assertEquals(200, response.getStatus());
+
+ Files.delete(tmpPath);
+ }
+
+ @Test
+ public void testFieldDeferredAndFileDeferred() throws Exception
+ {
+ String value = "text";
+ Charset encoding = StandardCharsets.US_ASCII;
+ byte[] fileData = new byte[1024];
+ new Random().nextBytes(fileData);
+ start(new AbstractMultiPartHandler()
+ {
+ @Override
+ protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ List parts = new ArrayList<>(request.getParts());
+ Assert.assertEquals(2, parts.size());
+ Part fieldPart = parts.get(0);
+ Part filePart = parts.get(1);
+ if (!"field".equals(fieldPart.getName()))
+ {
+ Part swap = filePart;
+ filePart = fieldPart;
+ fieldPart = swap;
+ }
+
+ Assert.assertEquals(value, IO.toString(fieldPart.getInputStream(), encoding));
+
+ Assert.assertEquals("file", filePart.getName());
+ Assert.assertEquals("application/octet-stream", filePart.getContentType());
+ Assert.assertEquals("fileName", filePart.getSubmittedFileName());
+ Assert.assertArrayEquals(fileData, IO.readBytes(filePart.getInputStream()));
+ }
+ });
+
+ MultiPartContentProvider multiPart = new MultiPartContentProvider();
+ DeferredContentProvider fieldContent = new DeferredContentProvider();
+ multiPart.addFieldPart("field", fieldContent, null);
+ DeferredContentProvider fileContent = new DeferredContentProvider();
+ multiPart.addFilePart("file", "fileName", fileContent, null);
+ CountDownLatch responseLatch = new CountDownLatch(1);
+ client.newRequest("localhost", connector.getLocalPort())
+ .scheme(scheme)
+ .method(HttpMethod.POST)
+ .content(multiPart)
+ .send(result ->
+ {
+ if (result.isSucceeded())
+ {
+ Assert.assertEquals(200, result.getResponse().getStatus());
+ responseLatch.countDown();
+ }
+ });
+
+ // Wait until the request has been sent.
+ Thread.sleep(1000);
+
+ // Provide the content, in reversed part order.
+ fileContent.offer(ByteBuffer.wrap(fileData));
+ fileContent.close();
+
+ Thread.sleep(1000);
+
+ fieldContent.offer(encoding.encode(value));
+ fieldContent.close();
+
+ multiPart.close();
+
+ Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ private static abstract class AbstractMultiPartHandler extends AbstractHandler
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ File tmpDir = MavenTestingUtils.getTargetTestingDir();
+ request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement(tmpDir.getAbsolutePath()));
+ handle(request, response);
+ }
+
+ protected abstract void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
+ }
+}
diff --git a/jetty-client/src/test/resources/jetty-logging.properties b/jetty-client/src/test/resources/jetty-logging.properties
index 1c19e5331e5..5f8794e83fa 100644
--- a/jetty-client/src/test/resources/jetty-logging.properties
+++ b/jetty-client/src/test/resources/jetty-logging.properties
@@ -1,3 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
+#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
\ No newline at end of file
diff --git a/jetty-continuation/pom.xml b/jetty-continuation/pom.xml
index ecb449e63a0..edd16b1f70c 100644
--- a/jetty-continuation/pom.xml
+++ b/jetty-continuation/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-continuation
diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml
index 08169311d6e..9f94d9e73b3 100644
--- a/jetty-deploy/pom.xml
+++ b/jetty-deploy/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-deploy
@@ -14,24 +14,6 @@
-
- org.apache.felix
- maven-bundle-plugin
- true
-
-
-
- manifest
-
-
-
- org.eclipse.jetty.jmx.*;resolution:=optional,*
- <_nouses>true
-
-
-
-
- org.codehaus.mojofindbugs-maven-plugin
diff --git a/jetty-deploy/src/main/config/modules/deploy.mod b/jetty-deploy/src/main/config/modules/deploy.mod
index f567a2090f0..794868bfb48 100644
--- a/jetty-deploy/src/main/config/modules/deploy.mod
+++ b/jetty-deploy/src/main/config/modules/deploy.mod
@@ -1,6 +1,5 @@
-#
-# Deploy Feature
-#
+[description]
+Enables webapplication deployment from the webapps directory.
[depend]
webapp
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/NamePredicate.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugListenerBinding.java
similarity index 50%
rename from jetty-start/src/main/java/org/eclipse/jetty/start/graph/NamePredicate.java
rename to jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugListenerBinding.java
index db1755991d1..cc8698cf505 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/NamePredicate.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugListenerBinding.java
@@ -16,20 +16,38 @@
// ========================================================================
//
-package org.eclipse.jetty.start.graph;
+package org.eclipse.jetty.deploy.bindings;
-public class NamePredicate implements Predicate
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.server.DebugListener;
+
+
+/** A Deployment binding that installs a DebugListener in all deployed contexts
+ */
+public class DebugListenerBinding extends DebugBinding
{
- private final String name;
-
- public NamePredicate(String name)
+ final DebugListener _debugListener;
+
+ public DebugListenerBinding()
{
- this.name = name;
+ this(new DebugListener());
}
- @Override
- public boolean match(Node> input)
+ public DebugListenerBinding(DebugListener debugListener)
{
- return input.getName().equalsIgnoreCase(this.name);
+ super(new String[]{"deploying"});
+ _debugListener=debugListener;
}
+
+ public DebugListener getDebugListener()
+ {
+ return _debugListener;
+ }
+
+ public void processBinding(Node node, App app) throws Exception
+ {
+ app.getContextHandler().addEventListener(_debugListener);
+ }
+
}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java
index 0c4121896f1..15289d5016f 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/GlobalWebappConfigBinding.java
@@ -97,6 +97,8 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding
Resource resource = Resource.newResource(app.getOriginId());
File file = resource.getFile();
jettyXmlConfig.getIdMap().put("Server",app.getDeploymentManager().getServer());
+ jettyXmlConfig.getProperties().put("jetty.home",System.getProperty("jetty.home","."));
+ jettyXmlConfig.getProperties().put("jetty.base",System.getProperty("jetty.base","."));
jettyXmlConfig.getProperties().put("jetty.webapp",file.getCanonicalPath());
jettyXmlConfig.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
index 5cda93f2db9..91f70c95390 100644
--- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/WebAppProvider.java
@@ -257,7 +257,7 @@ public class WebAppProvider extends ScanningAppProvider
if (resource.exists() && FileID.isXmlFile(file))
{
- XmlConfiguration xmlc = new XmlConfiguration(resource.getURL())
+ XmlConfiguration xmlc = new XmlConfiguration(resource.getURI().toURL())
{
@Override
public void initializeDefaults(Object context)
diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml
index bdda3fcbc6f..9878feb1cef 100644
--- a/jetty-distribution/pom.xml
+++ b/jetty-distribution/pom.xml
@@ -3,7 +3,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOTjetty-distributionJetty :: Distribution Assemblies
@@ -441,8 +441,8 @@
copy-dependencies
- org.eclipse.jetty,org.eclipse.jetty.toolchain,org.mortbay.jasper,org.eclipse.jetty.orbit
- apache-jsp,apache-el,org.eclipse.jdt.core
+ org.eclipse.jetty,org.eclipse.jetty.toolchain,org.mortbay.jasper,org.eclipse.jetty.orbit,org.eclipse.jdt.core.compiler
+ apache-jsp,apache-el,ecjjartrue${assembly-directory}/lib/apache-jsp
@@ -710,6 +710,11 @@
jetty-proxy${project.version}
+
+ org.eclipse.jetty
+ jetty-unixsocket
+ ${project.version}
+ org.eclipse.jetty.fcgifcgi-server
@@ -778,6 +783,11 @@
jetty-infinispan${project.version}
+
+ org.eclipse.jetty.gcloud
+ gcloud-session-manager
+ ${project.version}
+ org.eclipse.jettyjetty-nosql
diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh
index 8cbb41ce170..21c2c979f4b 100755
--- a/jetty-distribution/src/main/resources/bin/jetty.sh
+++ b/jetty-distribution/src/main/resources/bin/jetty.sh
@@ -166,7 +166,7 @@ then
ETC=$HOME/etc
fi
-for CONFIG in $ETC/default/${NAME}{,9} $HOME/.${NAME}rc; do
+for CONFIG in {/etc,~/etc}/default/${NAME}{,9} $HOME/.${NAME}rc; do
if [ -f "$CONFIG" ] ; then
readConfig "$CONFIG"
fi
@@ -445,7 +445,7 @@ case "$ACTION" in
exit 1
fi
- if [ -n "$JETTY_USER" ]
+ if [ -n "$JETTY_USER" ] && [ `whoami` != "$JETTY_USER" ]
then
unset SU_SHELL
if [ "$JETTY_SHELL" ]
@@ -457,11 +457,11 @@ case "$ACTION" in
chown "$JETTY_USER" "$JETTY_PID"
# FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
su - "$JETTY_USER" $SU_SHELL -c "
- exec ${RUN_CMD[*]} start-log-file="$JETTY_LOGS/start.log" &
+ exec ${RUN_CMD[*]} start-log-file="$JETTY_LOGS/start.log" > /dev/null &
disown \$!
echo \$! > '$JETTY_PID'"
else
- "${RUN_CMD[@]}" &
+ "${RUN_CMD[@]}" > /dev/null &
disown $!
echo $! > "$JETTY_PID"
fi
diff --git a/jetty-distribution/src/main/resources/modules/hawtio.mod b/jetty-distribution/src/main/resources/modules/hawtio.mod
index f6d0d9d511b..fcc34d15048 100644
--- a/jetty-distribution/src/main/resources/modules/hawtio.mod
+++ b/jetty-distribution/src/main/resources/modules/hawtio.mod
@@ -1,6 +1,5 @@
-#
-# Hawtio x module
-#
+[description]
+Deploys the Hawtio console as a webapplication.
[depend]
stats
diff --git a/jetty-distribution/src/main/resources/modules/jamon.mod b/jetty-distribution/src/main/resources/modules/jamon.mod
index 2d1f144d1b7..77cc3d1e9d7 100644
--- a/jetty-distribution/src/main/resources/modules/jamon.mod
+++ b/jetty-distribution/src/main/resources/modules/jamon.mod
@@ -1,6 +1,5 @@
-#
-# JAMon Jetty module
-#
+[description]
+Deploys the JAMon webapplication
[depend]
stats
diff --git a/jetty-distribution/src/main/resources/modules/jminix.mod b/jetty-distribution/src/main/resources/modules/jminix.mod
index 05788f09159..81a75c7350e 100644
--- a/jetty-distribution/src/main/resources/modules/jminix.mod
+++ b/jetty-distribution/src/main/resources/modules/jminix.mod
@@ -1,6 +1,5 @@
-#
-# JaMON Jetty module
-#
+[description]
+Deploys the Jminix JMX Console within the server
[depend]
stats
diff --git a/jetty-distribution/src/main/resources/modules/jolokia.mod b/jetty-distribution/src/main/resources/modules/jolokia.mod
index da8ac8f8c2e..efe8a59185e 100644
--- a/jetty-distribution/src/main/resources/modules/jolokia.mod
+++ b/jetty-distribution/src/main/resources/modules/jolokia.mod
@@ -1,6 +1,5 @@
-#
-# Jolokia Jetty module
-#
+[description]
+Deploys the Jolokia console as a web application.
[depend]
stats
diff --git a/jetty-distribution/src/main/resources/modules/jsp.mod b/jetty-distribution/src/main/resources/modules/jsp.mod
index a16cc93dc93..2bc7ba85225 100644
--- a/jetty-distribution/src/main/resources/modules/jsp.mod
+++ b/jetty-distribution/src/main/resources/modules/jsp.mod
@@ -1,6 +1,5 @@
-#
-# Jetty JSP Module
-#
+[description]
+Enables JSP for all webapplications deployed on the server.
[depend]
servlet
diff --git a/jetty-distribution/src/main/resources/modules/jstl.mod b/jetty-distribution/src/main/resources/modules/jstl.mod
index efc310af6e9..dedb2c052c1 100644
--- a/jetty-distribution/src/main/resources/modules/jstl.mod
+++ b/jetty-distribution/src/main/resources/modules/jstl.mod
@@ -1,6 +1,5 @@
-#
-# Jetty JSTL Module
-#
+[description]
+Enables JSTL for all webapplications deployed on the server
[depend]
jsp
diff --git a/jetty-distribution/src/main/resources/modules/setuid.mod b/jetty-distribution/src/main/resources/modules/setuid.mod
index 41ef757e82d..c1174ccba8d 100644
--- a/jetty-distribution/src/main/resources/modules/setuid.mod
+++ b/jetty-distribution/src/main/resources/modules/setuid.mod
@@ -1,6 +1,7 @@
-#
-# Set UID Feature
-#
+[description]
+Enables the unix setUID configuration so that the server
+may be started as root to open privileged ports/files before
+changing to a restricted user (eg jetty).
[depend]
server
diff --git a/jetty-fcgi/fcgi-client/pom.xml b/jetty-fcgi/fcgi-client/pom.xml
index ecce8fd8655..8cf9e9a68b6 100644
--- a/jetty-fcgi/fcgi-client/pom.xml
+++ b/jetty-fcgi/fcgi-client/pom.xml
@@ -3,7 +3,7 @@
org.eclipse.jetty.fcgifcgi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
index 4be9cbf1960..4d9691de506 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
@@ -181,7 +181,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
if (channels.isEmpty())
close();
else
- failAndClose(new EOFException());
+ failAndClose(new EOFException(String.valueOf(getEndPoint())));
}
@Override
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java
index f6adf480c4a..2f3447d384c 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpDestinationOverFCGI.java
@@ -22,8 +22,9 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.PoolingHttpDestination;
+import org.eclipse.jetty.client.api.Connection;
-public class HttpDestinationOverFCGI extends PoolingHttpDestination
+public class HttpDestinationOverFCGI extends PoolingHttpDestination
{
public HttpDestinationOverFCGI(HttpClient client, Origin origin)
{
@@ -31,8 +32,8 @@ public class HttpDestinationOverFCGI extends PoolingHttpDestination
+public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination
{
public MultiplexHttpDestinationOverFCGI(HttpClient client, Origin origin)
{
@@ -31,8 +32,8 @@ public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestinationParser for the BEGIN_REQUEST frame body.
Depending on the {@code type}, the content may have a different format,
+ * so there are specialized content parsers.
+ *
+ * @see HeaderParser
+ * @see ContentParser
+ */
public abstract class Parser
{
+ private static final Logger LOG = Log.getLogger(Parser.class);
+
protected final HeaderParser headerParser = new HeaderParser();
private State state = State.HEADER;
private int padding;
@@ -56,6 +80,9 @@ public abstract class Parser
else
{
ContentParser.Result result = contentParser.parse(buffer);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Parsed request {} content {} result={}", headerParser.getRequest(), headerParser.getFrameType(), result);
+
if (result == ContentParser.Result.PENDING)
{
// Not enough data, signal to read/parse more.
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
index a31ad2d5233..6d0fefae64f 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
@@ -18,11 +18,13 @@
package org.eclipse.jetty.fcgi.parser;
+import java.io.EOFException;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@@ -32,6 +34,14 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+/**
+ *
The parser for STDOUT type frame bodies.
+ *
STDOUT frame bodies contain both the HTTP headers (but not the response line)
+ * and the HTTP content (either Content-Length delimited or chunked).
+ *
For this reason, a special HTTP parser is used to parse the frames body.
+ * This special HTTP parser is configured to skip the response line, and to
+ * parse HTTP headers and HTTP content.
+ */
public class ResponseContentParser extends StreamContentParser
{
private static final Logger LOG = Log.getLogger(ResponseContentParser.class);
@@ -89,12 +99,12 @@ public class ResponseContentParser extends StreamContentParser
public boolean parse(ByteBuffer buffer)
{
- if (LOG.isDebugEnabled())
- LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
-
int remaining = buffer.remaining();
while (remaining > 0)
{
+ if (LOG.isDebugEnabled())
+ LOG.debug("Response {} {}, state {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
+
switch (state)
{
case HEADERS:
@@ -245,12 +255,12 @@ public class ResponseContentParser extends StreamContentParser
{
if (!seenResponseCode)
{
- // No Status header but we have other headers, assume 200 OK
+ // No Status header but we have other headers, assume 200 OK.
notifyBegin(200, "OK");
notifyHeaders(fields);
}
notifyHeaders();
- // Return from parsing so that we can parse the content
+ // Return from HTTP parsing so that we can parse the content.
return true;
}
@@ -277,21 +287,34 @@ public class ResponseContentParser extends StreamContentParser
@Override
public boolean messageComplete()
{
- // Return from parsing so that we can parse the next headers or the raw content.
- // No need to notify the listener because it will be done by FCGI_END_REQUEST.
- return true;
+ // No need to notify the end of the response to the
+ // listener because it will be done by FCGI_END_REQUEST.
+ return false;
}
@Override
public void earlyEOF()
{
- // TODO
+ fail(new EOFException());
}
@Override
public void badMessage(int status, String reason)
{
- // TODO
+ fail(new BadMessageException(status, reason));
+ }
+
+ protected void fail(Throwable failure)
+ {
+ try
+ {
+ listener.onFailure(request, failure);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Exception while invoking listener " + listener, x);
+ }
}
}
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
index a341013c2e0..70602a626a7 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
@@ -24,6 +24,10 @@ import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+/**
+ *
A stream content parser parses frame bodies of type STDIN, STDOUT and STDERR.
+ *
STDOUT frame bodies are handled specially by {@link ResponseContentParser}.
+ */
public class StreamContentParser extends ContentParser
{
private static final Logger LOG = Log.getLogger(StreamContentParser.class);
diff --git a/jetty-fcgi/fcgi-server/pom.xml b/jetty-fcgi/fcgi-server/pom.xml
index cbad4c07f2b..271ef70033b 100644
--- a/jetty-fcgi/fcgi-server/pom.xml
+++ b/jetty-fcgi/fcgi-server/pom.xml
@@ -3,7 +3,7 @@
org.eclipse.jetty.fcgifcgi-parent
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0
diff --git a/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod b/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod
index 14152d5f2bd..6a4beaf1ab1 100644
--- a/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod
+++ b/jetty-fcgi/fcgi-server/src/main/config/modules/fcgi.mod
@@ -1,6 +1,5 @@
-#
-# FastCGI Module
-#
+[description]
+Adds the FastCGI implementation to the classpath.
[depend]
servlet
diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
index d80124e6e5a..8587042304c 100644
--- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
+++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java
@@ -20,8 +20,11 @@ package org.eclipse.jetty.fcgi.server.proxy;
import java.net.URI;
import java.util.List;
+import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
@@ -32,6 +35,7 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
@@ -66,6 +70,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
{
public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
+ public static final String ORIGINAL_URI_ATTRIBUTE_INIT_PARAM = "originalURIAttribute";
public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS";
private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
@@ -77,6 +82,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
private Pattern scriptPattern;
+ private String originalURIAttribute;
private boolean fcgiHTTPS;
@Override
@@ -89,6 +95,8 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
value = "(.+?\\.php)";
scriptPattern = Pattern.compile(value);
+ originalURIAttribute = getInitParameter(ORIGINAL_URI_ATTRIBUTE_INIT_PARAM);
+
fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM));
}
@@ -110,24 +118,33 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName());
proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr());
proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort()));
-
proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme());
- // If we are forwarded or included, retain the original request URI.
- String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
- String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
- if (originalPath == null)
+ // Has the original URI been rewritten ?
+ String originalURI = null;
+ if (originalURIAttribute != null)
+ originalURI = (String)request.getAttribute(originalURIAttribute);
+
+ if (originalURI == null)
{
- originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
- originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
+ // If we are forwarded or included, retain the original request URI.
+ String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+ String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
+ if (originalPath == null)
+ {
+ originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
+ originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
+ }
+ if (originalPath != null)
+ {
+ originalURI = originalPath;
+ if (originalQuery != null)
+ originalURI += "?" + originalQuery;
+ }
}
- if (originalPath != null)
- {
- String originalURI = originalPath;
- if (originalQuery != null)
- originalURI += "?" + originalQuery;
+
+ if (originalURI != null)
proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI);
- }
// If the Host header is missing, add it.
if (!proxyRequest.getHeaders().containsKey(HttpHeader.HOST.asString()))
@@ -212,6 +229,16 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
{
super.customize(request, fastCGIHeaders);
customizeFastCGIHeaders(request, fastCGIHeaders);
+ if (_log.isDebugEnabled())
+ {
+ TreeMap fcgi = new TreeMap<>();
+ for (HttpField field : fastCGIHeaders)
+ fcgi.put(field.getName(), field.getValue());
+ String eol = System.lineSeparator();
+ _log.debug("FastCGI variables{}{}", eol, fcgi.entrySet().stream()
+ .map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue()))
+ .collect(Collectors.joining(eol)));
+ }
}
}
}
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java
index 9c83445108f..35b75f5dda6 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/AbstractHttpClientServerTest.java
@@ -18,12 +18,9 @@
package org.eclipse.jetty.fcgi.server;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
import java.util.concurrent.atomic.AtomicLong;
-import org.eclipse.jetty.client.ConnectionPool;
+import org.eclipse.jetty.client.DuplexConnectionPool;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.LeakTrackingConnectionPool;
@@ -40,9 +37,12 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.LeakDetector;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Rule;
+import static org.junit.Assert.assertThat;
+
public abstract class AbstractHttpClientServerTest
{
@Rule
@@ -71,7 +71,7 @@ public abstract class AbstractHttpClientServerTest
QueuedThreadPool executor = new QueuedThreadPool();
executor.setName(executor.getName() + "-client");
-
+
client = new HttpClient(new HttpClientTransportOverFCGI(1, false, "")
{
@Override
@@ -80,7 +80,7 @@ public abstract class AbstractHttpClientServerTest
return new HttpDestinationOverFCGI(client, origin)
{
@Override
- protected ConnectionPool newConnectionPool(HttpClient client)
+ protected DuplexConnectionPool newConnectionPool(HttpClient client)
{
return new LeakTrackingConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
{
@@ -105,15 +105,15 @@ public abstract class AbstractHttpClientServerTest
{
System.gc();
- assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), is(0L));
- assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), is(0L));
- assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), is(0L));
-
- assertThat("Client BufferPool - leaked acquires", clientBufferPool.getLeakedAcquires(), is(0L));
- assertThat("Client BufferPool - leaked releases", clientBufferPool.getLeakedReleases(), is(0L));
- assertThat("Client BufferPool - unreleased", clientBufferPool.getLeakedResources(), is(0L));
-
- assertThat("Connection Leaks", connectionLeaks.get(), is(0L));
+ assertThat("Server BufferPool - leaked acquires", serverBufferPool.getLeakedAcquires(), Matchers.is(0L));
+ assertThat("Server BufferPool - leaked releases", serverBufferPool.getLeakedReleases(), Matchers.is(0L));
+ assertThat("Server BufferPool - unreleased", serverBufferPool.getLeakedResources(), Matchers.is(0L));
+
+ assertThat("Client BufferPool - leaked acquires", clientBufferPool.getLeakedAcquires(), Matchers.is(0L));
+ assertThat("Client BufferPool - leaked releases", clientBufferPool.getLeakedReleases(), Matchers.is(0L));
+ assertThat("Client BufferPool - unreleased", clientBufferPool.getLeakedResources(), Matchers.is(0L));
+
+ assertThat("Connection Leaks", connectionLeaks.get(), Matchers.is(0L));
if (client != null)
client.stop();
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
index cac16c8519a..135dd7a74ec 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
@@ -95,6 +95,35 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
}
+ @Test
+ public void testGETResponseWithBigContent() throws Exception
+ {
+ final byte[] data = new byte[16 * 1024 * 1024];
+ new Random().nextBytes(data);
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ // Setting the Content-Length triggers the HTTP
+ // content mode for response content parsing,
+ // otherwise the RAW content mode is used.
+ response.setContentLength(data.length);
+ response.getOutputStream().write(data);
+ baseRequest.setHandled(true);
+ }
+ });
+
+ Request request = client.newRequest(scheme + "://localhost:" + connector.getLocalPort());
+ FutureResponseListener listener = new FutureResponseListener(request, data.length);
+ request.send(listener);
+ ContentResponse response = listener.get(15, TimeUnit.SECONDS);
+ Assert.assertNotNull(response);
+ Assert.assertEquals(200, response.getStatus());
+ byte[] content = response.getContent();
+ Assert.assertArrayEquals(data, content);
+ }
+
@Test
public void testGETWithParametersResponseWithContent() throws Exception
{
@@ -420,7 +449,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
-
+
@Test
public void testConnectionIdleTimeout() throws Exception
{
diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
index a5923c7470d..c6dae98fd98 100644
--- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
+++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java
@@ -19,11 +19,9 @@
package org.eclipse.jetty.fcgi.server.proxy;
import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Random;
import java.util.concurrent.TimeUnit;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -32,7 +30,6 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
import org.eclipse.jetty.server.HttpConfiguration;
@@ -40,7 +37,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
@@ -51,9 +48,9 @@ import org.junit.runners.Parameterized;
public class FastCGIProxyServletTest
{
@Parameterized.Parameters
- public static Collection
Utility class that implement transparent proxy functionalities.
*
Configuration parameters:
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java
index 796da806601..6fbb9d87a92 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java
@@ -138,6 +138,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
return new ProxyWriter(clientRequest, proxyResponse);
}
+ @Override
protected Response.CompleteListener newProxyResponseListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse)
{
return new ProxyResponseListener(clientRequest, proxyResponse);
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
index 71a75bc8994..7b196626f37 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java
@@ -18,9 +18,11 @@
package org.eclipse.jetty.proxy;
+import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashSet;
@@ -44,6 +46,7 @@ import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.HttpTransport;
@@ -51,6 +54,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -160,21 +164,17 @@ public class ConnectHandler extends HandlerWrapper
protected void doStart() throws Exception
{
if (executor == null)
- {
- setExecutor(getServer().getThreadPool());
- }
+ executor = getServer().getThreadPool();
+
if (scheduler == null)
- {
- setScheduler(new ScheduledExecutorScheduler());
- addBean(getScheduler());
- }
+ addBean(scheduler = new ScheduledExecutorScheduler());
+
if (bufferPool == null)
- {
- setByteBufferPool(new MappedByteBufferPool());
- addBean(getByteBufferPool());
- }
+ addBean(bufferPool = new MappedByteBufferPool());
+
addBean(selector = newSelectorManager());
selector.setConnectTimeout(getConnectTimeout());
+
super.doStart();
}
@@ -191,16 +191,8 @@ public class ConnectHandler extends HandlerWrapper
String serverAddress = request.getRequestURI();
if (LOG.isDebugEnabled())
LOG.debug("CONNECT request for {}", serverAddress);
- try
- {
- handleConnect(baseRequest, request, response, serverAddress);
- }
- catch (Exception x)
- {
- // TODO
- LOG.warn("ConnectHandler " + baseRequest.getHttpURI() + " " + x);
- LOG.debug(x);
- }
+
+ handleConnect(baseRequest, request, response, serverAddress);
}
else
{
@@ -249,32 +241,40 @@ public class ConnectHandler extends HandlerWrapper
return;
}
- SocketChannel channel = SocketChannel.open();
- channel.socket().setTcpNoDelay(true);
- channel.configureBlocking(false);
-
- AsyncContext asyncContext = request.startAsync();
- asyncContext.setTimeout(0);
-
HttpTransport transport = baseRequest.getHttpChannel().getHttpTransport();
-
// TODO Handle CONNECT over HTTP2!
if (!(transport instanceof HttpConnection))
{
if (LOG.isDebugEnabled())
- LOG.debug("CONNECT forbidden for {}", transport);
+ LOG.debug("CONNECT not supported for {}", transport);
sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
return;
}
- InetSocketAddress address = newConnectAddress(host, port);
+ AsyncContext asyncContext = request.startAsync();
+ asyncContext.setTimeout(0);
+
if (LOG.isDebugEnabled())
- LOG.debug("Connecting to {}", address);
- ConnectContext connectContext = new ConnectContext(request, response, asyncContext, (HttpConnection)transport);
- if (channel.connect(address))
- selector.accept(channel, connectContext);
- else
- selector.connect(channel, connectContext);
+ LOG.debug("Connecting to {}:{}", host, port);
+
+ connectToServer(request, host, port, new Promise()
+ {
+ @Override
+ public void succeeded(SocketChannel channel)
+ {
+ ConnectContext connectContext = new ConnectContext(request, response, asyncContext, (HttpConnection)transport);
+ if (channel.isConnected())
+ selector.accept(channel, connectContext);
+ else
+ selector.connect(channel, connectContext);
+ }
+
+ @Override
+ public void failed(Throwable x)
+ {
+ onConnectFailure(request, response, asyncContext, x);
+ }
+ });
}
catch (Exception x)
{
@@ -282,37 +282,59 @@ public class ConnectHandler extends HandlerWrapper
}
}
- /* ------------------------------------------------------------ */
- /** Create the address the connect channel will connect to.
- * @param host The host from the connect request
- * @param port The port from the connect request
+ protected void connectToServer(HttpServletRequest request, String host, int port, Promise promise)
+ {
+ SocketChannel channel = null;
+ try
+ {
+ channel = SocketChannel.open();
+ channel.socket().setTcpNoDelay(true);
+ channel.configureBlocking(false);
+ InetSocketAddress address = newConnectAddress(host, port);
+ channel.connect(address);
+ promise.succeeded(channel);
+ }
+ catch (Throwable x)
+ {
+ close(channel);
+ promise.failed(x);
+ }
+ }
+
+ private void close(Closeable closeable)
+ {
+ try
+ {
+ if (closeable != null)
+ closeable.close();
+ }
+ catch (Throwable x)
+ {
+ LOG.ignore(x);
+ }
+ }
+
+ /**
+ * Creates the server address to connect to.
+ *
+ * @param host The host from the CONNECT request
+ * @param port The port from the CONNECT request
* @return The InetSocketAddress to connect to.
*/
protected InetSocketAddress newConnectAddress(String host, int port)
{
return new InetSocketAddress(host, port);
}
-
+
protected void onConnectSuccess(ConnectContext connectContext, UpstreamConnection upstreamConnection)
{
- HttpConnection httpConnection = connectContext.getHttpConnection();
- ByteBuffer requestBuffer = httpConnection.getRequestBuffer();
- ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
- int remaining = requestBuffer.remaining();
- if (remaining > 0)
- {
- buffer = bufferPool.acquire(remaining, requestBuffer.isDirect());
- BufferUtil.flipToFill(buffer);
- buffer.put(requestBuffer);
- buffer.flip();
- }
-
ConcurrentMap context = connectContext.getContext();
HttpServletRequest request = connectContext.getRequest();
prepareContext(request, context);
+ HttpConnection httpConnection = connectContext.getHttpConnection();
EndPoint downstreamEndPoint = httpConnection.getEndPoint();
- DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context, buffer);
+ DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context);
downstreamConnection.setInputBufferSize(getBufferSize());
upstreamConnection.setConnection(downstreamConnection);
@@ -324,6 +346,7 @@ public class ConnectHandler extends HandlerWrapper
sendConnectResponse(request, response, HttpServletResponse.SC_OK);
upgradeConnection(request, response, downstreamConnection);
+
connectContext.getAsyncContext().complete();
}
@@ -349,7 +372,8 @@ public class ConnectHandler extends HandlerWrapper
}
catch (IOException x)
{
- // TODO: nothing we can do, close the connection
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not send CONNECT response", x);
}
}
@@ -367,9 +391,9 @@ public class ConnectHandler extends HandlerWrapper
return true;
}
- protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap context, ByteBuffer buffer)
+ protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap context)
{
- return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context, buffer);
+ return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context);
}
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
@@ -396,13 +420,17 @@ public class ConnectHandler extends HandlerWrapper
*
* @param endPoint the endPoint to read from
* @param buffer the buffer to read data into
+ * @param context the context information related to the connection
* @return the number of bytes read (possibly 0 since the read is non-blocking)
* or -1 if the channel has been closed remotely
* @throws IOException if the endPoint cannot be read
*/
- protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
+ protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException
{
- return endPoint.fill(buffer);
+ int read = endPoint.fill(buffer);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} read {} bytes", this, read);
+ return read;
}
/**
@@ -411,8 +439,9 @@ public class ConnectHandler extends HandlerWrapper
* @param endPoint the endPoint to write to
* @param buffer the buffer to write
* @param callback the completion callback to invoke
+ * @param context the context information related to the connection
*/
- protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
+ protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback, ConcurrentMap context)
{
if (LOG.isDebugEnabled())
LOG.debug("{} writing {} bytes", this, buffer.remaining());
@@ -475,16 +504,18 @@ public class ConnectHandler extends HandlerWrapper
}
@Override
- protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
+ protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) throws IOException
{
- return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout());
+ SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler());
+ endp.setIdleTimeout(getIdleTimeout());
+ return endp;
}
@Override
- public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+ public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
if (ConnectHandler.LOG.isDebugEnabled())
- ConnectHandler.LOG.debug("Connected to {}", channel.getRemoteAddress());
+ ConnectHandler.LOG.debug("Connected to {}", ((SocketChannel)channel).getRemoteAddress());
ConnectContext connectContext = (ConnectContext)attachment;
UpstreamConnection connection = newUpstreamConnection(endpoint, connectContext);
connection.setInputBufferSize(getBufferSize());
@@ -492,16 +523,11 @@ public class ConnectHandler extends HandlerWrapper
}
@Override
- protected void connectionFailed(SocketChannel channel, final Throwable ex, final Object attachment)
+ protected void connectionFailed(SelectableChannel channel, final Throwable ex, final Object attachment)
{
- getExecutor().execute(new Runnable()
- {
- public void run()
- {
- ConnectContext connectContext = (ConnectContext)attachment;
- onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
- }
- });
+ close(channel);
+ ConnectContext connectContext = (ConnectContext)attachment;
+ onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
}
}
@@ -561,37 +587,36 @@ public class ConnectHandler extends HandlerWrapper
public void onOpen()
{
super.onOpen();
- getExecutor().execute(new Runnable()
- {
- public void run()
- {
- onConnectSuccess(connectContext, UpstreamConnection.this);
- fillInterested();
- }
- });
+ onConnectSuccess(connectContext, UpstreamConnection.this);
+ fillInterested();
}
@Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
{
- return ConnectHandler.this.read(endPoint, buffer);
+ return ConnectHandler.this.read(endPoint, buffer, getContext());
}
@Override
protected void write(EndPoint endPoint, ByteBuffer buffer,Callback callback)
{
- ConnectHandler.this.write(endPoint, buffer, callback);
+ ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}
}
- public class DownstreamConnection extends ProxyConnection
+ public class DownstreamConnection extends ProxyConnection implements Connection.UpgradeTo
{
- private final ByteBuffer buffer;
+ private ByteBuffer buffer;
- public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap context, ByteBuffer buffer)
+ public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap context)
{
super(endPoint, executor, bufferPool, context);
- this.buffer = buffer;
+ }
+
+ @Override
+ public void onUpgradeTo(ByteBuffer buffer)
+ {
+ this.buffer = buffer == null ? BufferUtil.EMPTY_BUFFER : buffer;
}
@Override
@@ -623,13 +648,13 @@ public class ConnectHandler extends HandlerWrapper
@Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
{
- return ConnectHandler.this.read(endPoint, buffer);
+ return ConnectHandler.this.read(endPoint, buffer, getContext());
}
@Override
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
{
- ConnectHandler.this.write(endPoint, buffer, callback);
+ ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}
}
}
diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
index 51c0951284c..2c45df510e1 100644
--- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
+++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java
@@ -93,6 +93,7 @@ public class ProxyServlet extends AbstractProxyServlet
return new ProxyInputStreamContentProvider(request, response, proxyRequest, request.getInputStream());
}
+ @Override
protected Response.Listener newProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
{
return new ProxyResponseListener(request, response);
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
index 27ae1d9dac4..86ed0031b3a 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/AsyncMiddleManServletTest.java
@@ -18,9 +18,44 @@
package org.eclipse.jetty.proxy;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
-import org.eclipse.jetty.client.api.*;
+import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.FutureResponseListener;
@@ -49,27 +84,6 @@ import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.*;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.*;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
public class AsyncMiddleManServletTest
{
private static final Logger LOG = Log.getLogger(AsyncMiddleManServletTest.class);
@@ -1105,7 +1119,6 @@ public class AsyncMiddleManServletTest
// Send only part of the content; the proxy will idle timeout.
final byte[] data = new byte[]{'c', 'a', 'f', 'e'};
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
- .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
.content(new BytesContentProvider(data)
{
@Override
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java
index f601975408d..780e70686ab 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ConnectHandlerTest.java
@@ -27,6 +27,8 @@ import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.concurrent.ConcurrentMap;
@@ -36,12 +38,15 @@ import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.io.EndPoint;
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.toolchain.test.http.SimpleHttpResponse;
import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -630,6 +635,13 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
return super.handleAuthentication(request, response, address);
}
+ @Override
+ protected void connectToServer(HttpServletRequest request, String host, int port, Promise promise)
+ {
+ Assert.assertEquals(contextValue, request.getAttribute(contextKey));
+ super.connectToServer(request, host, port, promise);
+ }
+
@Override
protected void prepareContext(HttpServletRequest request, ConcurrentMap context)
{
@@ -637,6 +649,20 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
Assert.assertEquals(contextValue, request.getAttribute(contextKey));
context.put(contextKey, request.getAttribute(contextKey));
}
+
+ @Override
+ protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException
+ {
+ Assert.assertEquals(contextValue, context.get(contextKey));
+ return super.read(endPoint, buffer, context);
+ }
+
+ @Override
+ protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback, ConcurrentMap context)
+ {
+ Assert.assertEquals(contextValue, context.get(contextKey));
+ super.write(endPoint, buffer, callback, context);
+ }
});
proxy.start();
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 2af96671d4a..c9160432cc1 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
@@ -30,6 +30,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -264,15 +265,18 @@ public class ProxyServletFailureTest
@Override
protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
{
- return new DeferredContentProvider()
+ DeferredContentProvider provider = new DeferredContentProvider()
{
@Override
public boolean offer(ByteBuffer buffer, Callback callback)
{
- // Ignore all content to trigger the test condition.
- return true;
+ // Send less content to trigger the test condition.
+ buffer.limit(buffer.limit() - 1);
+ return super.offer(buffer.slice(), callback);
}
};
+ request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, provider));
+ return provider;
}
};
}
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
index 1f96b48a518..4d2dcc92330 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java
@@ -61,6 +61,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.client.DuplexConnectionPool;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.client.HttpProxy;
@@ -1081,7 +1082,8 @@ public class ProxyServletTest
Assert.assertEquals(-1, input.read());
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
- Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size());
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Assert.assertEquals(0, connectionPool.getIdleConnections().size());
}
@Test
@@ -1154,7 +1156,8 @@ public class ProxyServletTest
}
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", port);
- Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size());
+ DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
+ Assert.assertEquals(0, connectionPool.getIdleConnections().size());
}
@Test
diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
index caec5832783..fd333cd88e8 100644
--- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
+++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyTunnellingTest.java
@@ -18,8 +18,6 @@
package org.eclipse.jetty.proxy;
-import static org.junit.Assert.assertEquals;
-
import java.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
@@ -56,6 +54,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
@@ -64,6 +63,8 @@ import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
public class ProxyTunnellingTest
{
@Rule
@@ -87,7 +88,10 @@ public class ProxyTunnellingTest
sslContextFactory.setKeyStorePath(keyStorePath);
sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setKeyManagerPassword("keypwd");
- server = new Server();
+
+ QueuedThreadPool serverThreads = new QueuedThreadPool();
+ serverThreads.setName("server");
+ server = new Server(serverThreads);
serverConnector = new ServerConnector(server, sslContextFactory);
server.addConnector(serverConnector);
server.setHandler(handler);
@@ -101,7 +105,9 @@ public class ProxyTunnellingTest
protected void startProxy(ConnectHandler connectHandler) throws Exception
{
- proxy = new Server();
+ QueuedThreadPool proxyThreads = new QueuedThreadPool();
+ proxyThreads.setName("proxy");
+ proxy = new Server(proxyThreads);
proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector);
// Under Windows, it takes a while to detect that a connection
@@ -136,7 +142,7 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
public void testOneExchangeViaSSL() throws Exception
{
startSSLServer(new ServerHandler());
@@ -167,7 +173,7 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
public void testTwoExchangesViaSSL() throws Exception
{
startSSLServer(new ServerHandler());
@@ -210,7 +216,7 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
public void testTwoConcurrentExchangesViaSSL() throws Exception
{
startSSLServer(new ServerHandler());
@@ -278,7 +284,60 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
+ public void testShortIdleTimeoutOverriddenByRequest() throws Exception
+ {
+ // Short idle timeout for HttpClient.
+ long idleTimeout = 500;
+
+ startSSLServer(new ServerHandler());
+ startProxy(new ConnectHandler()
+ {
+ @Override
+ protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
+ {
+ try
+ {
+ // Make sure the proxy remains idle enough.
+ Thread.sleep(2 * idleTimeout);
+ super.handleConnect(baseRequest, request, response, serverAddress);
+ }
+ catch (InterruptedException x)
+ {
+ onConnectFailure(request, response, null, x);
+ }
+ }
+ });
+
+ HttpClient httpClient = new HttpClient(sslContextFactory);
+ httpClient.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort()));
+ // Short idle timeout for HttpClient.
+ httpClient.setIdleTimeout(idleTimeout);
+ httpClient.start();
+
+ try
+ {
+ String host = "localhost";
+ String body = "BODY";
+ ContentResponse response = httpClient.newRequest(host, serverConnector.getLocalPort())
+ .scheme(HttpScheme.HTTPS.asString())
+ .method(HttpMethod.GET)
+ .path("/echo?body=" + URLEncoder.encode(body, "UTF-8"))
+ // Long idle timeout for the request.
+ .idleTimeout(10 * idleTimeout, TimeUnit.MILLISECONDS)
+ .send();
+
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ String content = response.getContentAsString();
+ assertEquals(body, content);
+ }
+ finally
+ {
+ httpClient.stop();
+ }
+ }
+
+ @Test(timeout=60000)
public void testProxyDown() throws Exception
{
startSSLServer(new ServerHandler());
@@ -310,7 +369,7 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
public void testServerDown() throws Exception
{
startSSLServer(new ServerHandler());
@@ -342,7 +401,7 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
public void testProxyClosesConnection() throws Exception
{
startSSLServer(new ServerHandler());
@@ -376,7 +435,7 @@ public class ProxyTunnellingTest
}
}
- @Test
+ @Test(timeout=60000)
@Ignore("External Proxy Server no longer stable enough for testing")
public void testExternalProxy() throws Exception
{
diff --git a/jetty-quickstart/pom.xml b/jetty-quickstart/pom.xml
index 5cf47c97ce0..be039807125 100644
--- a/jetty-quickstart/pom.xml
+++ b/jetty-quickstart/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0org.eclipse.jetty
diff --git a/jetty-quickstart/src/main/config/modules/quickstart.mod b/jetty-quickstart/src/main/config/modules/quickstart.mod
index 4e59dd08624..cefa5f16887 100644
--- a/jetty-quickstart/src/main/config/modules/quickstart.mod
+++ b/jetty-quickstart/src/main/config/modules/quickstart.mod
@@ -1,6 +1,6 @@
-#
-# Jetty Quickstart module
-#
+[description]
+Enables the Jetty Quickstart module for rapid
+deployment of preconfigured webapplications.
[depend]
server
diff --git a/jetty-rewrite/pom.xml b/jetty-rewrite/pom.xml
index 50a6a7febdc..0bd15bb35a8 100644
--- a/jetty-rewrite/pom.xml
+++ b/jetty-rewrite/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-rewrite
diff --git a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
index 43ae117c17d..65a5dea44a2 100644
--- a/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
+++ b/jetty-rewrite/src/main/config/etc/jetty-rewrite.xml
@@ -19,21 +19,29 @@
+
+
+
+
+ REQUEST
+ ASYNC
+
+
+
+
+
+
-
-
-
-
diff --git a/jetty-rewrite/src/main/config/modules/rewrite.mod b/jetty-rewrite/src/main/config/modules/rewrite.mod
index c8a1750618a..3b741a1a0d0 100644
--- a/jetty-rewrite/src/main/config/modules/rewrite.mod
+++ b/jetty-rewrite/src/main/config/modules/rewrite.mod
@@ -1,6 +1,6 @@
-#
-# Jetty Rewrite module
-#
+[description]
+Enables the jetty-rewrite handler. Specific rewrite
+rules must be added to etc/jetty-rewrite.xml
[depend]
server
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
index 3eab973187e..20c1a87b579 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
@@ -173,7 +173,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
public class RewriteHandler extends HandlerWrapper
{
private RuleContainer _rules;
- private EnumSet _dispatchTypes = EnumSet.of(DispatcherType.REQUEST);
+ private EnumSet _dispatchTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC);
/* ------------------------------------------------------------ */
public RewriteHandler()
diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java
index e7974964f87..ccad0867467 100644
--- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java
+++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RuleContainer.java
@@ -35,11 +35,13 @@ import org.eclipse.jetty.util.log.Logger;
*/
public class RuleContainer extends Rule
{
+ public static final String ORIGINAL_QUERYSTRING_ATTRIBUTE_SUFFIX = ".QUERYSTRING";
private static final Logger LOG = Log.getLogger(RuleContainer.class);
protected Rule[] _rules;
protected String _originalPathAttribute;
+ protected String _originalQueryStringAttribute;
protected boolean _rewriteRequestURI=true;
protected boolean _rewritePathInfo=true;
@@ -132,6 +134,7 @@ public class RuleContainer extends Rule
public void setOriginalPathAttribute(String originalPathAttribte)
{
_originalPathAttribute=originalPathAttribte;
+ _originalQueryStringAttribute = originalPathAttribte + ORIGINAL_QUERYSTRING_ATTRIBUTE_SUFFIX;
}
/**
@@ -157,18 +160,26 @@ public class RuleContainer extends Rule
protected String apply(String target, HttpServletRequest request, HttpServletResponse response) throws IOException
{
boolean original_set=_originalPathAttribute==null;
+
+ target = URIUtil.compactPath(target);
for (Rule rule : _rules)
{
String applied=rule.matchAndApply(target,request, response);
if (applied!=null)
- {
+ {
+ applied = URIUtil.compactPath(applied);
+
LOG.debug("applied {}",rule);
LOG.debug("rewrote {} to {}",target,applied);
if (!original_set)
{
original_set=true;
request.setAttribute(_originalPathAttribute, target);
+
+ String query = request.getQueryString();
+ if (query != null)
+ request.setAttribute(_originalQueryStringAttribute,query);
}
if (_rewriteRequestURI)
diff --git a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml
index 9ac510991d2..1afc8030966 100644
--- a/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml
+++ b/jetty-rewrite/src/test/resources/org.mortbay.jetty.rewrite.handler/jetty-rewrite.xml
@@ -240,7 +240,6 @@
Test Realm/etc/realm.properties
- 0
diff --git a/jetty-runner/pom.xml b/jetty-runner/pom.xml
index 63bcbb88e27..c7eb427888e 100644
--- a/jetty-runner/pom.xml
+++ b/jetty-runner/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-runner
@@ -15,13 +15,14 @@
http://www.eclipse.org/jetty
+
org.apache.maven.pluginsmaven-dependency-pluginunpack-dependencies
- package
+ prepare-packageunpack-dependencies
@@ -36,33 +37,34 @@
- org.apache.maven.plugins
-
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+ bundle-manifest
+ process-classes
+
+ manifest
+
+
+
+
+
+ org.eclipse.jetty.runner.Runner
+ !*
+
+
+
+
+
+ org.apache.maven.pluginsmaven-jar-plugin
-
-
- package
- package
-
- jar
-
-
-
-
- org.eclipse.jetty.runner.Runner
-
-
- development
- http://eclipse.org/jetty
- ${user.name}
- org.eclipse.jetty.runner
- Jetty Runner
- Mort Bay Consulting
-
-
-
-
-
+
+
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+
+
diff --git a/jetty-security/pom.xml b/jetty-security/pom.xml
index 4578803ec3f..f789288cb30 100644
--- a/jetty-security/pom.xml
+++ b/jetty-security/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-security
@@ -25,7 +25,7 @@
- javax.servlet.*;version="[2.6.0,3.2)",javax.security.cert,*
+ javax.servlet.*;version="[2.6.0,3.2)",javax.security.cert,org.eclipse.jetty*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))",*
diff --git a/jetty-security/src/main/config/modules/security.mod b/jetty-security/src/main/config/modules/security.mod
index ba3163275f5..3955fcfee84 100644
--- a/jetty-security/src/main/config/modules/security.mod
+++ b/jetty-security/src/main/config/modules/security.mod
@@ -1,6 +1,5 @@
-#
-# Jetty Security Module
-#
+[description]
+Adds servlet standard security handling to the classpath.
[depend]
server
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java
new file mode 100644
index 00000000000..696a378662b
--- /dev/null
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java
@@ -0,0 +1,248 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.security;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+import javax.servlet.ServletRequest;
+
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * AbstractLoginService
+ */
+public abstract class AbstractLoginService extends AbstractLifeCycle implements LoginService
+{
+ private static final Logger LOG = Log.getLogger(AbstractLoginService.class);
+
+ protected IdentityService _identityService=new DefaultIdentityService();
+ protected String _name;
+ protected boolean _fullValidate = false;
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * RolePrincipal
+ */
+ public static class RolePrincipal implements Principal,Serializable
+ {
+ private static final long serialVersionUID = 2998397924051854402L;
+ private final String _roleName;
+ public RolePrincipal(String name)
+ {
+ _roleName=name;
+ }
+ public String getName()
+ {
+ return _roleName;
+ }
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * UserPrincipal
+ */
+ public static class UserPrincipal implements Principal,Serializable
+ {
+ private static final long serialVersionUID = -6226920753748399662L;
+ private final String _name;
+ private final Credential _credential;
+
+
+ /* -------------------------------------------------------- */
+ public UserPrincipal(String name,Credential credential)
+ {
+ _name=name;
+ _credential=credential;
+ }
+
+ /* -------------------------------------------------------- */
+ public boolean authenticate(Object credentials)
+ {
+ return _credential!=null && _credential.check(credentials);
+ }
+
+ /* -------------------------------------------------------- */
+ public boolean authenticate (Credential c)
+ {
+ return(_credential != null && c != null && _credential.equals(c));
+ }
+
+ /* ------------------------------------------------------------ */
+ public String getName()
+ {
+ return _name;
+ }
+
+
+
+ /* -------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return _name;
+ }
+ }
+
+ /* ------------------------------------------------------------ */
+ protected abstract String[] loadRoleInfo (UserPrincipal user);
+
+ /* ------------------------------------------------------------ */
+ protected abstract UserPrincipal loadUserInfo (String username);
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#getName()
+ */
+ @Override
+ public String getName()
+ {
+ return _name;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the identityService.
+ * @param identityService the identityService to set
+ */
+ public void setIdentityService(IdentityService identityService)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _identityService = identityService;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set the name.
+ * @param name the name to set
+ */
+ public void setName(String name)
+ {
+ if (isRunning())
+ throw new IllegalStateException("Running");
+ _name = name;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String toString()
+ {
+ return this.getClass().getSimpleName()+"["+_name+"]";
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object, javax.servlet.ServletRequest)
+ */
+ @Override
+ public UserIdentity login(String username, Object credentials, ServletRequest request)
+ {
+ if (username == null)
+ return null;
+
+ UserPrincipal userPrincipal = loadUserInfo(username);
+ if (userPrincipal.authenticate(credentials))
+ {
+ //safe to load the roles
+ String[] roles = loadRoleInfo(userPrincipal);
+
+ Subject subject = new Subject();
+ subject.getPrincipals().add(userPrincipal);
+ subject.getPrivateCredentials().add(userPrincipal._credential);
+ if (roles!=null)
+ for (String role : roles)
+ subject.getPrincipals().add(new RolePrincipal(role));
+ subject.setReadOnly();
+ return _identityService.newUserIdentity(subject,userPrincipal,roles);
+ }
+
+ return null;
+
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#validate(org.eclipse.jetty.server.UserIdentity)
+ */
+ @Override
+ public boolean validate(UserIdentity user)
+ {
+ if (!isFullValidate())
+ return true; //if we have a user identity it must be valid
+
+ //Do a full validation back against the user store
+ UserPrincipal fresh = loadUserInfo(user.getUserPrincipal().getName());
+ if (fresh == null)
+ return false; //user no longer exists
+
+ if (user.getUserPrincipal() instanceof UserPrincipal)
+ {
+ System.err.println("VALIDATING user "+fresh.getName());
+ return fresh.authenticate(((UserPrincipal)user.getUserPrincipal())._credential);
+ }
+
+ throw new IllegalStateException("UserPrincipal not KnownUser"); //can't validate
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#getIdentityService()
+ */
+ @Override
+ public IdentityService getIdentityService()
+ {
+ return _identityService;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.security.LoginService#logout(org.eclipse.jetty.server.UserIdentity)
+ */
+ @Override
+ public void logout(UserIdentity user)
+ {
+ //Override in subclasses
+
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isFullValidate()
+ {
+ return _fullValidate;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setFullValidate(boolean fullValidate)
+ {
+ _fullValidate = fullValidate;
+ }
+
+}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
index 423fcad9412..c509f3741f7 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java
@@ -43,7 +43,8 @@ public interface Authenticator
/* ------------------------------------------------------------ */
/**
* Configure the Authenticator
- * @param configuration
+ *
+ * @param configuration the configuration
*/
void setConfiguration(AuthConfiguration configuration);
@@ -64,13 +65,16 @@ public interface Authenticator
* where the http method of the original request causing authentication
* is not the same as the http method resulting from the redirect
* after authentication.
- * @param request
+ *
+ * @param request the request to manipulate
*/
void prepareRequest(ServletRequest request);
/* ------------------------------------------------------------ */
- /** Validate a request
+ /**
+ * Validate a request
+ *
* @param request The request
* @param response The response
* @param mandatory True if authentication is mandatory.
@@ -79,18 +83,20 @@ public interface Authenticator
* implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not manditory, then a
* {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
*
- * @throws ServerAuthException
+ * @throws ServerAuthException if unable to validate request
*/
Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
/* ------------------------------------------------------------ */
/**
- * @param request
- * @param response
- * @param mandatory
- * @param validatedUser
+ * is response secure
+ *
+ * @param request the request
+ * @param response the response
+ * @param mandatory if security is mandator
+ * @param validatedUser the user that was validated
* @return true if response is secure
- * @throws ServerAuthException
+ * @throws ServerAuthException if unable to test response
*/
boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
@@ -106,7 +112,8 @@ public interface Authenticator
String getAuthMethod();
String getRealmName();
- /** Get a SecurityHandler init parameter
+ /**
+ * Get a SecurityHandler init parameter
* @see SecurityHandler#getInitParameter(String)
* @param param parameter name
* @return Parameter value or null
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
index 15f64dc4304..d06898e845e 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintAware.java
@@ -29,15 +29,15 @@ public interface ConstraintAware
/* ------------------------------------------------------------ */
/** Set Constraint Mappings and roles.
* Can only be called during initialization.
- * @param constraintMappings
- * @param roles
+ * @param constraintMappings the mappings
+ * @param roles the roles
*/
void setConstraintMappings(List constraintMappings, Set roles);
/* ------------------------------------------------------------ */
/** Add a Constraint Mapping.
* May be called for running webapplication as an annotated servlet is instantiated.
- * @param mapping
+ * @param mapping the mapping
*/
void addConstraintMapping(ConstraintMapping mapping);
@@ -45,7 +45,7 @@ public interface ConstraintAware
/* ------------------------------------------------------------ */
/** Add a Role definition.
* May be called on running webapplication as an annotated servlet is instantiated.
- * @param role
+ * @param role the role
*/
void addRole(String role);
@@ -53,7 +53,7 @@ public interface ConstraintAware
* See Servlet Spec 31, sec 13.8.4, pg 145
* When true, requests with http methods not explicitly covered either by inclusion or omissions
* in constraints, will have access denied.
- * @param deny
+ * @param deny true for denied method access
*/
void setDenyUncoveredHttpMethods(boolean deny);
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
index 5d606528eeb..108ca0ad6f1 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java
@@ -19,6 +19,9 @@
package org.eclipse.jetty.security;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
import org.eclipse.jetty.security.PropertyUserStore.UserListener;
import org.eclipse.jetty.server.UserIdentity;
@@ -45,15 +48,15 @@ import org.eclipse.jetty.util.security.Credential;
*
* If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
*/
-public class HashLoginService extends MappedLoginService implements UserListener
+public class HashLoginService extends AbstractLoginService
{
private static final Logger LOG = Log.getLogger(HashLoginService.class);
- private PropertyUserStore _propertyUserStore;
- private String _config;
- private Resource _configResource;
- private Scanner _scanner;
- private boolean hotReload = false; // default is not to reload
+ protected PropertyUserStore _propertyUserStore;
+ protected String _config;
+ protected Resource _configResource;
+ protected boolean hotReload = false; // default is not to reload
+
/* ------------------------------------------------------------ */
public HashLoginService()
@@ -127,41 +130,45 @@ public class HashLoginService extends MappedLoginService implements UserListener
this.hotReload = enable;
}
- /* ------------------------------------------------------------ */
- /**
- * sets the refresh interval (in seconds)
- * @param sec the refresh interval
- * @deprecated use {@link #setHotReload(boolean)} instead
- */
- @Deprecated
- public void setRefreshInterval(int sec)
- {
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @return refresh interval in seconds for how often the properties file should be checked for changes
- * @deprecated use {@link #isHotReload()} instead
- */
- @Deprecated
- public int getRefreshInterval()
- {
- return (hotReload)?1:0;
- }
+
/* ------------------------------------------------------------ */
@Override
- protected UserIdentity loadUser(String username)
+ protected String[] loadRoleInfo(UserPrincipal user)
{
+ UserIdentity id = _propertyUserStore.getUserIdentity(user.getName());
+ if (id == null)
+ return null;
+
+
+ Set roles = id.getSubject().getPrincipals(RolePrincipal.class);
+ if (roles == null)
+ return null;
+
+ List list = new ArrayList<>();
+ for (RolePrincipal r:roles)
+ list.add(r.getName());
+
+ return list.toArray(new String[roles.size()]);
+ }
+
+
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected UserPrincipal loadUserInfo(String userName)
+ {
+ UserIdentity id = _propertyUserStore.getUserIdentity(userName);
+ if (id != null)
+ {
+ return (UserPrincipal)id.getUserPrincipal();
+ }
+
return null;
}
-
- /* ------------------------------------------------------------ */
- @Override
- public void loadUsers() throws IOException
- {
- // TODO: Consider refactoring MappedLoginService to not have to override with unused methods
- }
+
+
/* ------------------------------------------------------------ */
/**
@@ -180,7 +187,6 @@ public class HashLoginService extends MappedLoginService implements UserListener
_propertyUserStore = new PropertyUserStore();
_propertyUserStore.setHotReload(hotReload);
_propertyUserStore.setConfigPath(_config);
- _propertyUserStore.registerUserListener(this);
_propertyUserStore.start();
}
}
@@ -193,26 +199,5 @@ public class HashLoginService extends MappedLoginService implements UserListener
protected void doStop() throws Exception
{
super.doStop();
- if (_scanner != null)
- _scanner.stop();
- _scanner = null;
- }
-
- /* ------------------------------------------------------------ */
- @Override
- public void update(String userName, Credential credential, String[] roleArray)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("update: " + userName + " Roles: " + roleArray.length);
- putUser(userName,credential,roleArray);
- }
-
- /* ------------------------------------------------------------ */
- @Override
- public void remove(String userName)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("remove: " + userName);
- removeUser(userName);
}
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
index 089b8949117..7f38d07a490 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/JDBCLoginService.java
@@ -41,25 +41,18 @@ import org.eclipse.jetty.util.security.Credential;
/* ------------------------------------------------------------ */
/**
* HashMapped User Realm with JDBC as data source.
- * The login() method checks the inherited Map for the user. If the user is not
+ * The {@link #login(String, Object, ServletRequest)} method checks the inherited Map for the user. If the user is not
* found, it will fetch details from the database and populate the inherited
- * Map. It then calls the superclass login() method to perform the actual
+ * Map. It then calls the superclass {@link #login(String, Object, ServletRequest)} method to perform the actual
* authentication. Periodically (controlled by configuration parameter),
* internal hashes are cleared. Caching can be disabled by setting cache refresh
* interval to zero. Uses one database connection that is initialized at
- * startup. Reconnect on failures. authenticate() is 'synchronized'.
- *
+ * startup. Reconnect on failures.
+ *
* An example properties file for configuration is in
- * $JETTY_HOME/etc/jdbcRealm.properties
- *
- * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
- *
- *
- *
- *
+ * ${jetty.home}/etc/jdbcRealm.properties
*/
-
-public class JDBCLoginService extends MappedLoginService
+public class JDBCLoginService extends AbstractLoginService
{
private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
@@ -71,12 +64,30 @@ public class JDBCLoginService extends MappedLoginService
protected String _userTableKey;
protected String _userTablePasswordField;
protected String _roleTableRoleField;
- protected int _cacheTime;
- protected long _lastHashPurge;
protected Connection _con;
protected String _userSql;
protected String _roleSql;
+
+ /**
+ * JDBCKnownUser
+ */
+ public class JDBCUserPrincipal extends UserPrincipal
+ {
+ int _userKey;
+
+ public JDBCUserPrincipal(String name, Credential credential, int key)
+ {
+ super(name, credential);
+ _userKey = key;
+ }
+
+
+ public int getUserKey ()
+ {
+ return _userKey;
+ }
+ }
/* ------------------------------------------------------------ */
public JDBCLoginService()
@@ -110,9 +121,6 @@ public class JDBCLoginService extends MappedLoginService
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.security.MappedLoginService#doStart()
- */
@Override
protected void doStart() throws Exception
{
@@ -136,20 +144,18 @@ public class JDBCLoginService extends MappedLoginService
String _userRoleTable = properties.getProperty("userroletable");
String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
- _cacheTime = new Integer(properties.getProperty("cachetime"));
+
if (_jdbcDriver == null || _jdbcDriver.equals("")
|| _url == null
|| _url.equals("")
|| _userName == null
|| _userName.equals("")
- || _password == null
- || _cacheTime < 0)
+ || _password == null)
{
LOG.warn("UserRealm " + getName() + " has not been properly configured");
}
- _cacheTime *= 1000;
- _lastHashPurge = 0;
+
_userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
_roleSql = "select r." + _roleTableRoleField
+ " from "
@@ -164,7 +170,7 @@ public class JDBCLoginService extends MappedLoginService
+ " = u."
+ _userRoleTableRoleKey;
- Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
+ Loader.loadClass(_jdbcDriver).newInstance();
super.doStart();
}
@@ -209,30 +215,11 @@ public class JDBCLoginService extends MappedLoginService
}
}
- /* ------------------------------------------------------------ */
- @Override
- public UserIdentity login(String username, Object credentials, ServletRequest request)
- {
- long now = System.currentTimeMillis();
- if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
- {
- _users.clear();
- _lastHashPurge = now;
- closeConnection();
- }
-
- return super.login(username,credentials, request);
- }
+
+
/* ------------------------------------------------------------ */
- @Override
- protected void loadUsers()
- {
- }
-
- /* ------------------------------------------------------------ */
- @Override
- protected UserIdentity loadUser(String username)
+ public UserPrincipal loadUserInfo (String username)
{
try
{
@@ -251,18 +238,8 @@ public class JDBCLoginService extends MappedLoginService
{
int key = rs1.getInt(_userTableKey);
String credentials = rs1.getString(_userTablePasswordField);
- List roles = new ArrayList();
- try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
- {
- stat2.setInt(1, key);
- try (ResultSet rs2 = stat2.executeQuery())
- {
- while (rs2.next())
- roles.add(rs2.getString(_roleTableRoleField));
- }
- }
- return putUser(username, credentials, roles.toArray(new String[roles.size()]));
+ return new JDBCUserPrincipal (username, Credential.getCredential(credentials), key);
}
}
}
@@ -272,16 +249,60 @@ public class JDBCLoginService extends MappedLoginService
LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
closeConnection();
}
+
+ return null;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public String[] loadRoleInfo (UserPrincipal user)
+ {
+ JDBCUserPrincipal jdbcUser = (JDBCUserPrincipal)user;
+
+ try
+ {
+ if (null == _con)
+ connectDatabase();
+
+ if (null == _con)
+ throw new SQLException("Can't connect to database");
+
+
+ List roles = new ArrayList();
+
+ try (PreparedStatement stat2 = _con.prepareStatement(_roleSql))
+ {
+ stat2.setInt(1, jdbcUser.getUserKey());
+ try (ResultSet rs2 = stat2.executeQuery())
+ {
+ while (rs2.next())
+ roles.add(rs2.getString(_roleTableRoleField));
+ return roles.toArray(new String[roles.size()]);
+ }
+ }
+ }
+ catch (SQLException e)
+ {
+ LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
+ closeConnection();
+ }
+
return null;
}
- /* ------------------------------------------------------------ */
- protected UserIdentity putUser (String username, String credentials, String[] roles)
- {
- return putUser(username, Credential.getCredential(credentials),roles);
- }
-
+ /* ------------------------------------------------------------ */
+ /**
+ * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+ */
+ @Override
+ protected void doStop() throws Exception
+ {
+ closeConnection();
+ super.doStop();
+ }
+
+ /* ------------------------------------------------------------ */
/**
* Close an existing connection
*/
@@ -294,5 +315,4 @@ public class JDBCLoginService extends MappedLoginService
}
_con = null;
}
-
}
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
deleted file mode 100644
index 70b4c953294..00000000000
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/MappedLoginService.java
+++ /dev/null
@@ -1,344 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.security;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.security.Principal;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import javax.security.auth.Subject;
-import javax.servlet.ServletRequest;
-
-import org.eclipse.jetty.server.UserIdentity;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.eclipse.jetty.util.security.Credential;
-
-
-
-/* ------------------------------------------------------------ */
-/**
- * A login service that keeps UserIdentities in a concurrent map
- * either as the source or a cache of the users.
- *
- */
-public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
-{
- private static final Logger LOG = Log.getLogger(MappedLoginService.class);
-
- protected IdentityService _identityService=new DefaultIdentityService();
- protected String _name;
- protected final ConcurrentMap _users=new ConcurrentHashMap();
-
- /* ------------------------------------------------------------ */
- protected MappedLoginService()
- {
- }
-
- /* ------------------------------------------------------------ */
- /** Get the name.
- * @return the name
- */
- public String getName()
- {
- return _name;
- }
-
- /* ------------------------------------------------------------ */
- /** Get the identityService.
- * @return the identityService
- */
- public IdentityService getIdentityService()
- {
- return _identityService;
- }
-
- /* ------------------------------------------------------------ */
- /** Get the users.
- * @return the users
- */
- public ConcurrentMap getUsers()
- {
- return _users;
- }
-
- /* ------------------------------------------------------------ */
- /** Set the identityService.
- * @param identityService the identityService to set
- */
- public void setIdentityService(IdentityService identityService)
- {
- if (isRunning())
- throw new IllegalStateException("Running");
- _identityService = identityService;
- }
-
- /* ------------------------------------------------------------ */
- /** Set the name.
- * @param name the name to set
- */
- public void setName(String name)
- {
- if (isRunning())
- throw new IllegalStateException("Running");
- _name = name;
- }
-
- /* ------------------------------------------------------------ */
- /** Set the users.
- * @param users the users to set
- */
- public void setUsers(Map users)
- {
- if (isRunning())
- throw new IllegalStateException("Running");
- _users.clear();
- _users.putAll(users);
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
- */
- @Override
- protected void doStart() throws Exception
- {
- loadUsers();
- super.doStart();
- }
-
- /* ------------------------------------------------------------ */
- @Override
- protected void doStop() throws Exception
- {
- super.doStop();
- }
-
- /* ------------------------------------------------------------ */
- public void logout(UserIdentity identity)
- {
- LOG.debug("logout {}",identity);
- }
-
- /* ------------------------------------------------------------ */
- @Override
- public String toString()
- {
- return this.getClass().getSimpleName()+"["+_name+"]";
- }
-
- /* ------------------------------------------------------------ */
- /** Put user into realm.
- * Called by implementations to put the user data loaded from
- * file/db etc into the user structure.
- * @param userName User name
- * @param info a UserIdentity instance, or a String password or Credential instance
- * @return User instance
- */
- protected synchronized UserIdentity putUser(String userName, Object info)
- {
- final UserIdentity identity;
- if (info instanceof UserIdentity)
- identity=(UserIdentity)info;
- else
- {
- Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
-
- Principal userPrincipal = new KnownUser(userName,credential);
- Subject subject = new Subject();
- subject.getPrincipals().add(userPrincipal);
- subject.getPrivateCredentials().add(credential);
- subject.setReadOnly();
- identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
- }
-
- _users.put(userName,identity);
- return identity;
- }
-
- /* ------------------------------------------------------------ */
- /** Put user into realm.
- * @param userName The user to add
- * @param credential The users Credentials
- * @param roles The users roles
- * @return UserIdentity
- */
- public synchronized UserIdentity putUser(String userName, Credential credential, String[] roles)
- {
- Principal userPrincipal = new KnownUser(userName,credential);
- Subject subject = new Subject();
- subject.getPrincipals().add(userPrincipal);
- subject.getPrivateCredentials().add(credential);
-
- if (roles!=null)
- for (String role : roles)
- subject.getPrincipals().add(new RolePrincipal(role));
-
- subject.setReadOnly();
- UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
- _users.put(userName,identity);
- return identity;
- }
-
- /* ------------------------------------------------------------ */
- public void removeUser(String username)
- {
- _users.remove(username);
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object, ServletRequest)
- */
- public UserIdentity login(String username, Object credentials, ServletRequest request)
- {
- if (username == null)
- return null;
-
- UserIdentity user = _users.get(username);
-
- if (user==null)
- user = loadUser(username);
-
- if (user!=null)
- {
- UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
- if (principal.authenticate(credentials))
- return user;
- }
- return null;
- }
-
- /* ------------------------------------------------------------ */
- public boolean validate(UserIdentity user)
- {
- if (_users.containsKey(user.getUserPrincipal().getName()))
- return true;
-
- if (loadUser(user.getUserPrincipal().getName())!=null)
- return true;
-
- return false;
- }
-
- /* ------------------------------------------------------------ */
- protected abstract UserIdentity loadUser(String username);
-
- /* ------------------------------------------------------------ */
- protected abstract void loadUsers() throws IOException;
-
-
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- public interface UserPrincipal extends Principal,Serializable
- {
- boolean authenticate(Object credentials);
- public boolean isAuthenticated();
- }
-
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- public static class RolePrincipal implements Principal,Serializable
- {
- private static final long serialVersionUID = 2998397924051854402L;
- private final String _roleName;
- public RolePrincipal(String name)
- {
- _roleName=name;
- }
- public String getName()
- {
- return _roleName;
- }
- }
-
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- public static class Anonymous implements UserPrincipal,Serializable
- {
- private static final long serialVersionUID = 1097640442553284845L;
-
- public boolean isAuthenticated()
- {
- return false;
- }
-
- public String getName()
- {
- return "Anonymous";
- }
-
- public boolean authenticate(Object credentials)
- {
- return false;
- }
-
- }
-
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- public static class KnownUser implements UserPrincipal,Serializable
- {
- private static final long serialVersionUID = -6226920753748399662L;
- private final String _name;
- private final Credential _credential;
-
- /* -------------------------------------------------------- */
- public KnownUser(String name,Credential credential)
- {
- _name=name;
- _credential=credential;
- }
-
- /* -------------------------------------------------------- */
- public boolean authenticate(Object credentials)
- {
- return _credential!=null && _credential.check(credentials);
- }
-
- /* ------------------------------------------------------------ */
- public String getName()
- {
- return _name;
- }
-
- /* -------------------------------------------------------- */
- public boolean isAuthenticated()
- {
- return true;
- }
-
- /* -------------------------------------------------------- */
- @Override
- public String toString()
- {
- return _name;
- }
- }
-}
-
diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
index 0bab9329574..2d7a6368a64 100644
--- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
+++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java
@@ -33,8 +33,7 @@ import java.util.Set;
import javax.security.auth.Subject;
-import org.eclipse.jetty.security.MappedLoginService.KnownUser;
-import org.eclipse.jetty.security.MappedLoginService.RolePrincipal;
+
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.PathWatcher;
import org.eclipse.jetty.util.PathWatcher.PathWatchEvent;
@@ -64,17 +63,17 @@ public class PropertyUserStore extends AbstractLifeCycle implements PathWatcher.
{
private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
- private Path _configPath;
- private Resource _configResource;
+ protected Path _configPath;
+ protected Resource _configResource;
- private PathWatcher pathWatcher;
- private boolean hotReload = false; // default is not to reload
+ protected PathWatcher pathWatcher;
+ protected boolean hotReload = false; // default is not to reload
- private IdentityService _identityService = new DefaultIdentityService();
- private boolean _firstLoad = true; // true if first load, false from that point on
- private final List _knownUsers = new ArrayList();
- private final Map _knownUserIdentities = new HashMap();
- private List _listeners;
+ protected IdentityService _identityService = new DefaultIdentityService();
+ protected boolean _firstLoad = true; // true if first load, false from that point on
+ protected final List _knownUsers = new ArrayList();
+ protected final Map _knownUserIdentities = new HashMap();
+ protected List _listeners;
/**
* Get the config (as a string)
@@ -186,27 +185,7 @@ public class PropertyUserStore extends AbstractLifeCycle implements PathWatcher.
this.hotReload = enable;
}
- /* ------------------------------------------------------------ */
- /**
- * sets the refresh interval (in seconds)
- * @param sec the refresh interval
- * @deprecated use {@link #setHotReload(boolean)} instead
- */
- @Deprecated
- public void setRefreshInterval(int sec)
- {
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @return refresh interval in seconds for how often the properties file should be checked for changes
- * @deprecated use {@link #isHotReload()} instead
- */
- @Deprecated
- public int getRefreshInterval()
- {
- return (hotReload)?1:0;
- }
+
@Override
public String toString()
@@ -221,7 +200,7 @@ public class PropertyUserStore extends AbstractLifeCycle implements PathWatcher.
}
/* ------------------------------------------------------------ */
- private void loadUsers() throws IOException
+ protected void loadUsers() throws IOException
{
if (_configPath == null)
return;
@@ -259,7 +238,7 @@ public class PropertyUserStore extends AbstractLifeCycle implements PathWatcher.
known.add(username);
Credential credential = Credential.getCredential(credentials);
- Principal userPrincipal = new KnownUser(username,credential);
+ Principal userPrincipal = new AbstractLoginService.UserPrincipal(username,credential);
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(credential);
@@ -268,7 +247,7 @@ public class PropertyUserStore extends AbstractLifeCycle implements PathWatcher.
{
for (String role : roleArray)
{
- subject.getPrincipals().add(new RolePrincipal(role));
+ subject.getPrincipals().add(new AbstractLoginService.RolePrincipal(role));
}
}
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java
index 7da1a9b8892..abf0c7ace5a 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/AliasedConstraintTest.java
@@ -62,7 +62,8 @@ public class AliasedConstraintTest
private static Server server;
private static LocalConnector connector;
private static ConstraintSecurityHandler security;
-
+
+
@BeforeClass
public static void startServer() throws Exception
{
@@ -73,7 +74,8 @@ public class AliasedConstraintTest
ContextHandler context = new ContextHandler();
SessionHandler session = new SessionHandler();
- HashLoginService loginService = new HashLoginService(TEST_REALM);
+ TestLoginService loginService = new TestLoginService(TEST_REALM);
+
loginService.putUser("user0",new Password("password"),new String[] {});
loginService.putUser("user",new Password("password"),new String[] { "user" });
loginService.putUser("user2",new Password("password"),new String[] { "user" });
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
index 4a37e6c82cd..288bd7df882 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
@@ -85,7 +85,8 @@ public class ConstraintTest
ContextHandler _context = new ContextHandler();
SessionHandler _session = new SessionHandler();
- HashLoginService _loginService = new HashLoginService(TEST_REALM);
+ TestLoginService _loginService = new TestLoginService(TEST_REALM);
+
_loginService.putUser("user0", new Password("password"), new String[]{});
_loginService.putUser("user",new Password("password"), new String[] {"user"});
_loginService.putUser("user2",new Password("password"), new String[] {"user"});
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
index 007d5ed978c..004eeab7552 100644
--- a/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/SpecExampleConstraintTest.java
@@ -69,8 +69,9 @@ public class SpecExampleConstraintTest
ContextHandler _context = new ContextHandler();
_session = new SessionHandler();
- HashLoginService _loginService = new HashLoginService(TEST_REALM);
- _loginService.putUser("fred",new Password("password"));
+ TestLoginService _loginService = new TestLoginService(TEST_REALM);
+
+ _loginService.putUser("fred",new Password("password"), IdentityService.NO_ROLES);
_loginService.putUser("harry",new Password("password"), new String[] {"HOMEOWNER"});
_loginService.putUser("chris",new Password("password"), new String[] {"CONTRACTOR"});
_loginService.putUser("steven", new Password("password"), new String[] {"SALESCLERK"});
diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/TestLoginService.java b/jetty-security/src/test/java/org/eclipse/jetty/security/TestLoginService.java
new file mode 100644
index 00000000000..222fe136e21
--- /dev/null
+++ b/jetty-security/src/test/java/org/eclipse/jetty/security/TestLoginService.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 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.security;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * TestLoginService
+ *
+ *
+ */
+public class TestLoginService extends AbstractLoginService
+{
+ protected Map _users = new HashMap<>();
+ protected Map _roles = new HashMap<>();
+
+
+
+ public TestLoginService(String name)
+ {
+ setName(name);
+ }
+
+ public void putUser (String username, Credential credential, String[] roles)
+ {
+ UserPrincipal userPrincipal = new UserPrincipal(username,credential);
+ _users.put(username, userPrincipal);
+ _roles.put(username, roles);
+ }
+
+ /**
+ * @see org.eclipse.jetty.security.AbstractLoginService#loadRoleInfo(org.eclipse.jetty.security.AbstractLoginService.UserPrincipal)
+ */
+ @Override
+ protected String[] loadRoleInfo(UserPrincipal user)
+ {
+ return _roles.get(user.getName());
+ }
+
+ /**
+ * @see org.eclipse.jetty.security.AbstractLoginService#loadUserInfo(java.lang.String)
+ */
+ @Override
+ protected UserPrincipal loadUserInfo(String username)
+ {
+ return _users.get(username);
+ }
+
+}
diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml
index 743d78a02c8..1572142ecd5 100644
--- a/jetty-server/pom.xml
+++ b/jetty-server/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-server
@@ -14,25 +14,6 @@
-
- org.apache.felix
- maven-bundle-plugin
- true
-
-
- generate-manifest
-
- manifest
-
-
-
- javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.jmx.*;resolution:=optional,*
- <_nouses>true
-
-
-
-
- org.apache.maven.pluginsmaven-jar-plugin
diff --git a/jetty-server/src/main/config/etc/jetty-debug.xml b/jetty-server/src/main/config/etc/jetty-debug.xml
new file mode 100644
index 00000000000..2e47a5f9ffc
--- /dev/null
+++ b/jetty-server/src/main/config/etc/jetty-debug.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+ /yyyy_mm_dd.debug.log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jetty-server/src/main/config/etc/jetty-http-forwarded.xml b/jetty-server/src/main/config/etc/jetty-http-forwarded.xml
new file mode 100644
index 00000000000..0aacbb2468b
--- /dev/null
+++ b/jetty-server/src/main/config/etc/jetty-http-forwarded.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml
index d7d295cef5b..8e6d1a4ae5d 100644
--- a/jetty-server/src/main/config/etc/jetty.xml
+++ b/jetty-server/src/main/config/etc/jetty.xml
@@ -88,11 +88,7 @@
-
+
diff --git a/jetty-server/src/main/config/modules/continuation.mod b/jetty-server/src/main/config/modules/continuation.mod
index 231c09d0f37..af03ae41ced 100644
--- a/jetty-server/src/main/config/modules/continuation.mod
+++ b/jetty-server/src/main/config/modules/continuation.mod
@@ -1,6 +1,7 @@
-#
-# Classic Jetty Continuation Support Module
-#
+[description]
+Enables support for Continuation style asynchronous
+Servlets. Now deprecated in favour of Servlet 3.1
+API
[lib]
lib/jetty-continuation-${jetty.version}.jar
diff --git a/jetty-server/src/main/config/modules/debug.mod b/jetty-server/src/main/config/modules/debug.mod
new file mode 100644
index 00000000000..7b75ecc0e79
--- /dev/null
+++ b/jetty-server/src/main/config/modules/debug.mod
@@ -0,0 +1,30 @@
+[description]
+Enables the DebugListener to generate additional
+logging regarding detailed request handling events.
+Renames threads to include request URI.
+
+[depend]
+deploy
+
+[files]
+logs/
+
+[xml]
+etc/jetty-debug.xml
+
+[ini-template]
+
+## How many days to retain old log files
+# jetty.debug.retainDays=14
+
+## Timezone of the log entries
+# jetty.debug.timezone=GMT
+
+## Show Request/Response headers
+# jetty.debug.showHeaders=true
+
+## Rename threads while in context scope
+# jetty.debug.renameThread=false
+
+## Dump context as deployed
+# jetty.debug.dumpContext=true
diff --git a/jetty-server/src/main/config/modules/debuglog.mod b/jetty-server/src/main/config/modules/debuglog.mod
index ba8b60a7274..a76f728a5b4 100644
--- a/jetty-server/src/main/config/modules/debuglog.mod
+++ b/jetty-server/src/main/config/modules/debuglog.mod
@@ -1,6 +1,6 @@
-#
-# Debug module
-#
+[description]
+Deprecated Debug Log using the DebugHandle.
+Replaced with the debug module.
[depend]
server
diff --git a/jetty-server/src/main/config/modules/ext.mod b/jetty-server/src/main/config/modules/ext.mod
index 56b10f7ea42..4171f8dfc21 100644
--- a/jetty-server/src/main/config/modules/ext.mod
+++ b/jetty-server/src/main/config/modules/ext.mod
@@ -1,6 +1,6 @@
-#
-# Module to add all lib/ext/**.jar files to classpath
-#
+[description]
+Adds all jar files discovered in $JETTY_HOME/lib/ext
+and $JETTY_BASE/lib/ext to the servers classpath.
[lib]
lib/ext/**.jar
diff --git a/jetty-server/src/main/config/modules/gzip.mod b/jetty-server/src/main/config/modules/gzip.mod
index 1efc8346489..65663a16066 100644
--- a/jetty-server/src/main/config/modules/gzip.mod
+++ b/jetty-server/src/main/config/modules/gzip.mod
@@ -1,7 +1,6 @@
-#
-# GZIP module
-# Applies GzipHandler to entire server
-#
+[description]
+Enable GzipHandler for dynamic gzip compression
+for the entire server.
[depend]
server
diff --git a/jetty-server/src/main/config/modules/home-base-warning.mod b/jetty-server/src/main/config/modules/home-base-warning.mod
index 28e5757e815..3e599f0788f 100644
--- a/jetty-server/src/main/config/modules/home-base-warning.mod
+++ b/jetty-server/src/main/config/modules/home-base-warning.mod
@@ -1,6 +1,6 @@
-#
-# Home and Base Warning
-#
+[description]
+Generates a warning that server has been run from $JETTY_HOME
+rather than from a $JETTY_BASE.
[xml]
etc/home-base-warning.xml
diff --git a/jetty-server/src/main/config/modules/http-forwarded.mod b/jetty-server/src/main/config/modules/http-forwarded.mod
new file mode 100644
index 00000000000..60f10da7361
--- /dev/null
+++ b/jetty-server/src/main/config/modules/http-forwarded.mod
@@ -0,0 +1,20 @@
+[description]
+Adds a forwarded request customizer to the HTTP Connector
+to process forwarded-for style headers from a proxy.
+
+[depend]
+http
+
+[xml]
+etc/jetty-http-forwarded.xml
+
+[ini-template]
+### ForwardedRequestCustomizer Configuration
+
+# jetty.httpConfig.forwardedHostHeader=X-Forwarded-Host
+# jetty.httpConfig.forwardedServerHeader=X-Forwarded-Server
+# jetty.httpConfig.forwardedProtoHeader=X-Forwarded-Proto
+# jetty.httpConfig.forwardedForHeader=X-Forwarded-For
+# jetty.httpConfig.forwardedSslSessionIdHeader=
+# jetty.httpConfig.forwardedCipherSuiteHeader=
+
diff --git a/jetty-server/src/main/config/modules/http.mod b/jetty-server/src/main/config/modules/http.mod
index 01e986243e5..c59ee4b4d95 100644
--- a/jetty-server/src/main/config/modules/http.mod
+++ b/jetty-server/src/main/config/modules/http.mod
@@ -1,6 +1,7 @@
-#
-# Jetty HTTP Connector
-#
+[description]
+Enables a HTTP connector on the server.
+By default HTTP/1 is support, but HTTP2C can
+be added to the connector with the http2c module.
[depend]
server
diff --git a/jetty-server/src/main/config/modules/https.mod b/jetty-server/src/main/config/modules/https.mod
index 092e0d70c72..6ffbd69d0cc 100644
--- a/jetty-server/src/main/config/modules/https.mod
+++ b/jetty-server/src/main/config/modules/https.mod
@@ -1,12 +1,12 @@
-#
-# Jetty HTTPS Connector
-#
+[description]
+Adds HTTPS protocol support to the TLS(SSL) Connector
[depend]
ssl
[optional]
http2
+http-forwarded
[xml]
etc/jetty-https.xml
diff --git a/jetty-server/src/main/config/modules/ipaccess.mod b/jetty-server/src/main/config/modules/ipaccess.mod
index 956ea0f2e3a..68f04dfc576 100644
--- a/jetty-server/src/main/config/modules/ipaccess.mod
+++ b/jetty-server/src/main/config/modules/ipaccess.mod
@@ -1,6 +1,6 @@
-#
-# IPAccess module
-#
+[description]
+Enable the ipaccess handler to apply a white/black list
+control of the remote IP of requests.
[depend]
server
diff --git a/jetty-server/src/main/config/modules/jdbc-sessions.mod b/jetty-server/src/main/config/modules/jdbc-sessions.mod
index d77ff043e22..9fe2beba153 100644
--- a/jetty-server/src/main/config/modules/jdbc-sessions.mod
+++ b/jetty-server/src/main/config/modules/jdbc-sessions.mod
@@ -1,6 +1,5 @@
-#
-# Jetty JDBC Session module
-#
+[description]
+Enables JDBC Session management.
[depend]
annotations
@@ -9,7 +8,6 @@ webapp
[xml]
etc/jetty-jdbc-sessions.xml
-
[ini-template]
## JDBC Session config
diff --git a/jetty-server/src/main/config/modules/jvm.mod b/jetty-server/src/main/config/modules/jvm.mod
index 195521c57f5..296c1b6a2b5 100644
--- a/jetty-server/src/main/config/modules/jvm.mod
+++ b/jetty-server/src/main/config/modules/jvm.mod
@@ -1,3 +1,6 @@
+[description]
+A noop module that creates an ini template useful for
+setting JVM arguments (eg -Xmx )
[ini-template]
## JVM Configuration
## If JVM args are include in an ini file then --exec is needed
diff --git a/jetty-server/src/main/config/modules/lowresources.mod b/jetty-server/src/main/config/modules/lowresources.mod
index 2f765d9af2c..257829afd85 100644
--- a/jetty-server/src/main/config/modules/lowresources.mod
+++ b/jetty-server/src/main/config/modules/lowresources.mod
@@ -1,6 +1,7 @@
-#
-# Low Resources module
-#
+[description]
+Enables a low resource monitor on the server
+that can take actions if threads and/or connections
+cross configured threshholds.
[depend]
server
diff --git a/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod b/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod
index 764d24b847a..374763d0b5b 100644
--- a/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod
+++ b/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod
@@ -1,6 +1,9 @@
-#
-# PROXY Protocol Module - SSL
-#
+[description]
+Enables the Proxy Protocol on the TLS(SSL) Connector.
+http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
+This allows a Proxy operating in TCP mode to transport
+details of the proxied connection to the server.
+Both V1 and V2 versions of the protocol are supported.
[depend]
ssl
diff --git a/jetty-server/src/main/config/modules/proxy-protocol.mod b/jetty-server/src/main/config/modules/proxy-protocol.mod
index 9df2700f4e7..48820e5c145 100644
--- a/jetty-server/src/main/config/modules/proxy-protocol.mod
+++ b/jetty-server/src/main/config/modules/proxy-protocol.mod
@@ -1,6 +1,10 @@
-#
-# PROXY Protocol Module - HTTP
-#
+[description]
+Enables the Proxy Protocol on the HTTP Connector.
+http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
+This allows a proxy operating in TCP mode to
+transport details of the proxied connection to
+the server.
+Both V1 and V2 versions of the protocol are supported.
[depend]
http
diff --git a/jetty-server/src/main/config/modules/requestlog.mod b/jetty-server/src/main/config/modules/requestlog.mod
index e27b246ea24..c849f65f319 100644
--- a/jetty-server/src/main/config/modules/requestlog.mod
+++ b/jetty-server/src/main/config/modules/requestlog.mod
@@ -1,6 +1,5 @@
-#
-# Request Log module
-#
+[description]
+Enables a NCSA style request log.
[depend]
server
diff --git a/jetty-server/src/main/config/modules/resources.mod b/jetty-server/src/main/config/modules/resources.mod
index 8647d81325b..56489486408 100644
--- a/jetty-server/src/main/config/modules/resources.mod
+++ b/jetty-server/src/main/config/modules/resources.mod
@@ -1,6 +1,7 @@
-#
-# Module to add resources directory to classpath
-#
+[description]
+Adds the $JETTY_HOME/resources and/or $JETTY_BASE/resources
+directory to the server classpath. Useful for configuration
+property files (eg jetty-logging.properties)
[lib]
resources/
diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod
index 6857cca5e4c..19e21c56fe4 100644
--- a/jetty-server/src/main/config/modules/server.mod
+++ b/jetty-server/src/main/config/modules/server.mod
@@ -1,6 +1,5 @@
-#
-# Base Server Module
-#
+[description]
+Enables the core Jetty server on the classpath.
[optional]
jvm
@@ -64,6 +63,9 @@ etc/jetty.xml
## Maximum number of error dispatches to prevent looping
# jetty.httpConfig.maxErrorDispatches=10
+## Maximum time to block in total for a blocking IO operation (default -1 is to use idleTimeout on progress)
+# jetty.httpConfig.blockingTimeout=-1
+
### Server configuration
## Whether ctrl+c on the console gracefully stops the Jetty server
# jetty.server.stopAtShutdown=true
@@ -73,3 +75,4 @@ etc/jetty.xml
## Dump the state of the Jetty server, components, and webapps before shutdown
# jetty.server.dumpBeforeStop=false
+
diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod
index 292780a1cb4..acc8d380c9c 100644
--- a/jetty-server/src/main/config/modules/ssl.mod
+++ b/jetty-server/src/main/config/modules/ssl.mod
@@ -1,6 +1,7 @@
-#
-# SSL Keystore module
-#
+[description]
+Enables a TLS(SSL) Connector on the server.
+This may be used for HTTPS and/or HTTP2 by enabling
+the associated support modules.
[name]
ssl
diff --git a/jetty-server/src/main/config/modules/stats.mod b/jetty-server/src/main/config/modules/stats.mod
index 0922469cdfb..838d54a904d 100644
--- a/jetty-server/src/main/config/modules/stats.mod
+++ b/jetty-server/src/main/config/modules/stats.mod
@@ -1,6 +1,6 @@
-#
-# Stats module
-#
+[description]
+Enable detailed statistics collection for the server,
+available via JMX.
[depend]
server
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index bfd8206df9a..c46ed08be59 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -253,9 +253,11 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
protected void doStart() throws Exception
{
+ if(_defaultProtocol==null)
+ throw new IllegalStateException("No default protocol for "+this);
_defaultConnectionFactory = getConnectionFactory(_defaultProtocol);
if(_defaultConnectionFactory==null)
- throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol);
+ throw new IllegalStateException("No protocol factory for default protocol '"+_defaultProtocol+"' in "+this);
super.doStart();
@@ -298,7 +300,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
// If we have a stop timeout
long stopTimeout = getStopTimeout();
CountDownLatch stopping=_stopping;
- if (stopTimeout > 0 && stopping!=null)
+ if (stopTimeout > 0 && stopping!=null && getAcceptors()>0)
stopping.await(stopTimeout,TimeUnit.MILLISECONDS);
_stopping=null;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
index ab46bd52174..036b5142ad5 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java
@@ -142,7 +142,7 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement
buf.append("] \"");
append(buf,request.getMethod());
buf.append(' ');
- append(buf,request.getHttpURI().toString());
+ append(buf,request.getOriginalURI());
buf.append(' ');
append(buf,request.getProtocol());
buf.append("\" ");
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
index 483a41c50b7..66e28253020 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextState.java
@@ -33,11 +33,18 @@ import org.eclipse.jetty.server.handler.ContextHandler;
public class AsyncContextState implements AsyncContext
{
+ private final HttpChannel _channel;
volatile HttpChannelState _state;
public AsyncContextState(HttpChannelState state)
{
_state=state;
+ _channel=_state.getHttpChannel();
+ }
+
+ public HttpChannel getHttpChannel()
+ {
+ return _channel;
}
HttpChannelState state()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java
new file mode 100644
index 00000000000..955a6557752
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java
@@ -0,0 +1,333 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.ContextHandler.ContextScopeListener;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.annotation.Name;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/** A Context Listener that produces additional debug.
+ * This listener if added to a ContextHandler, will produce additional debug information to
+ * either/or a specific log stream or the standard debug log.
+ * The events produced by {@link ServletContextListener}, {@link ServletRequestListener},
+ * {@link AsyncListener} and {@link ContextScopeListener} are logged.
+ */
+@ManagedObject("Debug Listener")
+public class DebugListener extends AbstractLifeCycle implements ServletContextListener
+{
+ private static final Logger LOG = Log.getLogger(DebugListener.class);
+ private static final DateCache __date=new DateCache("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
+
+ private final String _attr = String.format("__R%s@%x",this.getClass().getSimpleName(),System.identityHashCode(this));
+
+ private final PrintStream _out;
+ private boolean _renameThread;
+ private boolean _showHeaders;
+ private boolean _dumpContext;
+
+ public DebugListener()
+ {
+ this(null,false,false,false);
+ }
+
+ public DebugListener(@Name("renameThread") boolean renameThread, @Name("showHeaders") boolean showHeaders, @Name("dumpContext") boolean dumpContext)
+ {
+ this(null,renameThread,showHeaders,dumpContext);
+ }
+
+ public DebugListener(@Name("outputStream") OutputStream out, @Name("renameThread") boolean renameThread, @Name("showHeaders") boolean showHeaders, @Name("dumpContext") boolean dumpContext)
+ {
+ _out=out==null?null:new PrintStream(out);
+ _renameThread=renameThread;
+ _showHeaders=showHeaders;
+ _dumpContext=dumpContext;
+ }
+
+ @ManagedAttribute("Rename thread within context scope")
+ public boolean isRenameThread()
+ {
+ return _renameThread;
+ }
+
+ public void setRenameThread(boolean renameThread)
+ {
+ _renameThread = renameThread;
+ }
+
+ @ManagedAttribute("Show request headers")
+ public boolean isShowHeaders()
+ {
+ return _showHeaders;
+ }
+
+ public void setShowHeaders(boolean showHeaders)
+ {
+ _showHeaders = showHeaders;
+ }
+
+ @ManagedAttribute("Dump contexts at start")
+ public boolean isDumpContext()
+ {
+ return _dumpContext;
+ }
+
+ public void setDumpContext(boolean dumpContext)
+ {
+ _dumpContext = dumpContext;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ sce.getServletContext().addListener(_servletRequestListener);
+ ContextHandler handler = ContextHandler.getContextHandler(sce.getServletContext());
+ handler.addEventListener(_contextScopeListener);
+ String cname=findContextName(sce.getServletContext());
+ log("^ ctx=%s %s",cname,sce.getServletContext());
+ if (_dumpContext)
+ {
+ if (_out==null)
+ handler.dumpStdErr();
+ else
+ {
+ try
+ {
+ handler.dump(_out);
+ }
+ catch(Exception e)
+ {
+ LOG.warn(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ String cname=findContextName(sce.getServletContext());
+ log("v ctx=%s %s",cname,sce.getServletContext());
+ }
+
+ protected String findContextName(ServletContext context)
+ {
+ if (context==null)
+ return null;
+ String n = (String)context.getAttribute(_attr);
+ if (n==null)
+ {
+ n=String.format("%s@%x",context.getContextPath(),context.hashCode());
+ context.setAttribute(_attr,n);
+ }
+ return n;
+ }
+
+ protected String findRequestName(ServletRequest request)
+ {
+ if (request==null)
+ return null;
+ HttpServletRequest r = (HttpServletRequest)request;
+ String n = (String)request.getAttribute(_attr);
+ if (n==null)
+ {
+ n=String.format("%s@%x",r.getRequestURI(),request.hashCode());
+ request.setAttribute(_attr,n);
+ }
+ return n;
+ }
+
+ protected void log(String format, Object... arg)
+ {
+ if (!isRunning())
+ return;
+
+ String s=String.format(format,arg);
+
+ long now = System.currentTimeMillis();
+ long ms = now%1000;
+ if (_out!=null)
+ _out.printf("%s.%03d:%s%n",__date.formatNow(now),ms,s);
+ if (LOG.isDebugEnabled())
+ LOG.info(s);
+ }
+
+ final AsyncListener _asyncListener = new AsyncListener()
+ {
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ String cname=findContextName(((AsyncContextEvent)event).getServletContext());
+ String rname=findRequestName(event.getAsyncContext().getRequest());
+ log("! ctx=%s r=%s onTimeout %s",cname,rname,((AsyncContextEvent)event).getHttpChannelState());
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
+ {
+ String cname=findContextName(((AsyncContextEvent)event).getServletContext());
+ String rname=findRequestName(event.getAsyncContext().getRequest());
+ log("! ctx=%s r=%s onStartAsync %s",cname,rname,((AsyncContextEvent)event).getHttpChannelState());
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ String cname=findContextName(((AsyncContextEvent)event).getServletContext());
+ String rname=findRequestName(event.getAsyncContext().getRequest());
+ log("!! ctx=%s r=%s onError %s %s",cname,rname,event.getThrowable(),((AsyncContextEvent)event).getHttpChannelState());
+ }
+
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
+ {
+ AsyncContextEvent ace=(AsyncContextEvent)event;
+ String cname=findContextName(ace.getServletContext());
+ String rname=findRequestName(ace.getAsyncContext().getRequest());
+
+ Request br=Request.getBaseRequest(ace.getAsyncContext().getRequest());
+ Response response = br.getResponse();
+ String headers=_showHeaders?("\n"+response.getHttpFields().toString()):"";
+
+ log("! ctx=%s r=%s onComplete %s %d%s",cname,rname,ace.getHttpChannelState(),response.getStatus(),headers);
+ }
+ };
+
+ final ServletRequestListener _servletRequestListener = new ServletRequestListener()
+ {
+ @Override
+ public void requestInitialized(ServletRequestEvent sre)
+ {
+ String cname=findContextName(sre.getServletContext());
+ HttpServletRequest r = (HttpServletRequest)sre.getServletRequest();
+
+ String rname=findRequestName(r);
+ DispatcherType d = r.getDispatcherType();
+ if (d==DispatcherType.REQUEST)
+ {
+ Request br=Request.getBaseRequest(r);
+
+ String headers=_showHeaders?("\n"+br.getMetaData().getFields().toString()):"";
+
+
+ StringBuffer url=r.getRequestURL();
+ if (r.getQueryString()!=null)
+ url.append('?').append(r.getQueryString());
+ log(">> %s ctx=%s r=%s %s %s %s %s %s%s",d,
+ cname,
+ rname,
+ d,
+ r.getMethod(),
+ url.toString(),
+ r.getProtocol(),
+ br.getHttpChannel(),
+ headers);
+ }
+ else
+ log(">> %s ctx=%s r=%s",d,cname,rname);
+ }
+
+ @Override
+ public void requestDestroyed(ServletRequestEvent sre)
+ {
+ String cname=findContextName(sre.getServletContext());
+ HttpServletRequest r = (HttpServletRequest)sre.getServletRequest();
+ String rname=findRequestName(r);
+ DispatcherType d = r.getDispatcherType();
+ if (sre.getServletRequest().isAsyncStarted())
+ {
+ sre.getServletRequest().getAsyncContext().addListener(_asyncListener);
+ log("<< %s ctx=%s r=%s async=true",d,cname,rname);
+ }
+ else
+ {
+ Request br=Request.getBaseRequest(r);
+ String headers=_showHeaders?("\n"+br.getResponse().getHttpFields().toString()):"";
+ log("<< %s ctx=%s r=%s async=false %d%s",d,cname,rname,Request.getBaseRequest(r).getResponse().getStatus(),headers);
+ }
+ }
+ };
+
+ final ContextHandler.ContextScopeListener _contextScopeListener = new ContextHandler.ContextScopeListener()
+ {
+ @Override
+ public void enterScope(Context context, Request request, Object reason)
+ {
+ String cname=findContextName(context);
+ if (request==null)
+ log("> ctx=%s %s",cname,reason);
+ else
+ {
+ String rname=findRequestName(request);
+
+ if (_renameThread)
+ {
+ Thread thread=Thread.currentThread();
+ thread.setName(String.format("%s#%s",thread.getName(),rname));
+ }
+
+ log("> ctx=%s r=%s %s",cname,rname,reason);
+ }
+ }
+
+
+ @Override
+ public void exitScope(Context context, Request request)
+ {
+ String cname=findContextName(context);
+ if (request==null)
+ log("< ctx=%s",cname);
+ else
+ {
+ String rname=findRequestName(request);
+
+ log("< ctx=%s r=%s",cname,rname);
+ if (_renameThread)
+ {
+ Thread thread=Thread.currentThread();
+ if (thread.getName().endsWith(rname))
+ thread.setName(thread.getName().substring(0,thread.getName().length()-rname.length()-1));
+ }
+ }
+ }
+ };
+}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
index 5ed9e13edda..1a04bd749a2 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java
@@ -31,16 +31,15 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
-import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.MultiMap;
public class Dispatcher implements RequestDispatcher
{
+ public final static String __ERROR_DISPATCH="org.eclipse.jetty.server.Dispatcher.ERROR";
+
/** Dispatch include attribute names */
public final static String __INCLUDE_PREFIX="javax.servlet.include.";
@@ -76,7 +75,15 @@ public class Dispatcher implements RequestDispatcher
public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
- forward(request, response, DispatcherType.ERROR);
+ try
+ {
+ request.setAttribute(__ERROR_DISPATCH,Boolean.TRUE);
+ forward(request, response, DispatcherType.ERROR);
+ }
+ finally
+ {
+ request.setAttribute(__ERROR_DISPATCH,null);
+ }
}
@Override
@@ -137,9 +144,7 @@ public class Dispatcher implements RequestDispatcher
request = new ServletRequestHttpWrapper(request);
if (!(response instanceof HttpServletResponse))
response = new ServletResponseHttpWrapper(response);
-
- final boolean old_handled=baseRequest.isHandled();
-
+
final HttpURI old_uri=baseRequest.getHttpURI();
final String old_context_path=baseRequest.getContextPath();
final String old_servlet_path=baseRequest.getServletPath();
@@ -151,7 +156,6 @@ public class Dispatcher implements RequestDispatcher
try
{
- baseRequest.setHandled(false);
baseRequest.setDispatcherType(dispatch);
if (_named!=null)
@@ -204,7 +208,6 @@ public class Dispatcher implements RequestDispatcher
}
finally
{
- baseRequest.setHandled(old_handled);
baseRequest.setHttpURI(old_uri);
baseRequest.setContextPath(old_context_path);
baseRequest.setServletPath(old_servlet_path);
@@ -216,28 +219,6 @@ public class Dispatcher implements RequestDispatcher
}
}
- @Deprecated
- public void push(ServletRequest request)
- {
- Request baseRequest = Request.getBaseRequest(request);
- HttpFields fields = new HttpFields(baseRequest.getHttpFields());
-
- String query=baseRequest.getQueryString();
- if (_uri.hasQuery())
- {
- if (query==null)
- query=_uri.getQuery();
- else
- query=query+"&"+_uri.getQuery(); // TODO is this correct semantic?
- }
-
- HttpURI uri = HttpURI.createHttpURI(request.getScheme(),request.getServerName(),request.getServerPort(),_uri.getPath(),baseRequest.getHttpURI().getParam(),query,null);
-
- MetaData.Request push = new MetaData.Request(HttpMethod.GET.asString(),uri,baseRequest.getHttpVersion(),fields);
-
- baseRequest.getHttpChannel().getHttpTransport().push(push);
- }
-
@Override
public String toString()
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
index 813ffd06d65..16aba09eecc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java
@@ -215,6 +215,7 @@ public class ForwardedRequestCustomizer implements Customizer
{
request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
request.setScheme(HttpScheme.HTTPS.asString());
+ request.setSecure(true);
}
}
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 4f8cce73a0a..942f12bde65 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,25 +18,24 @@
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;
import java.nio.channels.ClosedChannelException;
import java.util.List;
-import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.DispatcherType;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.UnavailableException;
-import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
@@ -44,6 +43,7 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.HttpChannelState.Action;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
@@ -262,6 +262,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
handle();
}
+ AtomicReference caller = new AtomicReference<>();
+
/**
* @return True if the channel is ready to continue handling (ie it is not suspended)
*/
@@ -295,7 +297,6 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
throw new IllegalStateException("state=" + _state);
_request.setHandled(false);
_response.getHttpOutput().reopen();
- _request.setDispatcherType(DispatcherType.REQUEST);
List customizers = _configuration.getCustomizers();
if (!customizers.isEmpty())
@@ -303,7 +304,15 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
for (HttpConfiguration.Customizer customizer : customizers)
customizer.customize(getConnector(), _configuration, _request);
}
- getServer().handle(this);
+ try
+ {
+ _request.setDispatcherType(DispatcherType.REQUEST);
+ getServer().handle(this);
+ }
+ finally
+ {
+ _request.setDispatcherType(null);
+ }
break;
}
@@ -311,67 +320,48 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
{
_request.setHandled(false);
_response.getHttpOutput().reopen();
- _request.setDispatcherType(DispatcherType.ASYNC);
- getServer().handleAsync(this);
+
+ try
+ {
+ _request.setDispatcherType(DispatcherType.ASYNC);
+ getServer().handleAsync(this);
+ }
+ finally
+ {
+ _request.setDispatcherType(null);
+ }
break;
}
case ERROR_DISPATCH:
{
- Throwable ex = _state.getAsyncContextEvent().getThrowable();
-
- // Check for error dispatch loops
- Integer loop_detect = (Integer)_request.getAttribute("org.eclipse.jetty.server.ERROR_DISPATCH");
- if (loop_detect==null)
- loop_detect=1;
- else
- loop_detect=loop_detect+1;
- _request.setAttribute("org.eclipse.jetty.server.ERROR_DISPATCH",loop_detect);
- if (loop_detect > getHttpConfiguration().getMaxErrorDispatches())
+ if (_response.isCommitted())
{
- LOG.warn("ERROR_DISPATCH loop detected on {} {}",_request,ex);
+ LOG.warn("Error Dispatch already committed");
+ _transport.abort((Throwable)_request.getAttribute(ERROR_EXCEPTION));
+ }
+ else
+ {
+ _response.reset();
+ Integer icode = (Integer)_request.getAttribute(ERROR_STATUS_CODE);
+ int code = icode!=null?icode.intValue():HttpStatus.INTERNAL_SERVER_ERROR_500;
+ _response.setStatus(code);
+ _request.setAttribute(ERROR_STATUS_CODE,code);
+ if (icode==null)
+ _request.setAttribute(ERROR_STATUS_CODE,code);
+ _request.setHandled(false);
+ _response.getHttpOutput().reopen();
+
try
{
- _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ _request.setDispatcherType(DispatcherType.ERROR);
+ getServer().handle(this);
}
finally
{
- _state.errorComplete();
+ _request.setDispatcherType(null);
}
- break loop;
}
-
- _request.setHandled(false);
- _response.resetBuffer();
- _response.getHttpOutput().reopen();
- _request.setDispatcherType(DispatcherType.ERROR);
-
- String reason;
- if (ex == null || ex instanceof TimeoutException)
- {
- reason = "Async Timeout";
- }
- else
- {
- reason = HttpStatus.Code.INTERNAL_SERVER_ERROR.getMessage();
- _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ex);
- }
-
- _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 500);
- _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, reason);
- _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, _request.getRequestURI());
-
- _response.setStatusWithReason(HttpStatus.INTERNAL_SERVER_ERROR_500, reason);
-
- ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(), _state.getContextHandler());
- if (eh instanceof ErrorHandler.ErrorPageMapper)
- {
- String error_page = ((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
- if (error_page != null)
- _state.getAsyncContextEvent().setDispatchPath(error_page);
- }
-
- getServer().handleAsync(this);
break;
}
@@ -395,24 +385,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
break;
}
- case ASYNC_ERROR:
- {
- _state.onError();
- break;
- }
-
case COMPLETE:
{
- // TODO do onComplete here for continuations to work
-// _state.onComplete();
-
if (!_response.isCommitted() && !_request.isHandled())
- _response.sendError(404);
+ _response.sendError(HttpStatus.NOT_FOUND_404);
else
_response.closeOutput();
_request.setHandled(true);
- // TODO do onComplete here to detect errors in final flush
_state.onComplete();
onCompleted();
@@ -426,26 +406,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
}
}
}
- catch (EofException|QuietServletException|BadMessageException e)
- {
- if (LOG.isDebugEnabled())
- LOG.debug(e);
- handleException(e);
- }
- catch (Throwable e)
- {
- if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
- {
- LOG.ignore(e);
- }
+ catch (Throwable failure)
+ {
+ if ("org.eclipse.jetty.continuation.ContinuationThrowable".equals(failure.getClass().getName()))
+ LOG.ignore(failure);
else
- {
- if (_connector.isStarted())
- LOG.warn(String.valueOf(_request.getHttpURI()), e);
- else
- LOG.debug(String.valueOf(_request.getHttpURI()), e);
- handleException(e);
- }
+ handleException(failure);
}
action = _state.unhandle();
@@ -458,6 +424,23 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
return !suspended;
}
+ protected void sendError(int code, String reason)
+ {
+ try
+ {
+ _response.sendError(code, reason);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Could not send error " + code + " " + reason, x);
+ }
+ finally
+ {
+ _state.errorComplete();
+ }
+ }
+
/**
*
Sends an error 500, performing a special logic to detect whether the request is suspended,
* to avoid concurrent writes from the application.
@@ -465,69 +448,61 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
* spawned thread writes the response content; in such case, we attempt to commit the error directly
* bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.
*
- * @param x the Throwable that caused the problem
+ * @param failure the Throwable that caused the problem
*/
- protected void handleException(Throwable x)
+ protected void handleException(Throwable failure)
{
- if (_state.isAsyncStarted())
+ // Unwrap wrapping Jetty exceptions.
+ if (failure instanceof RuntimeIOException)
+ failure = failure.getCause();
+
+ if (failure instanceof QuietServletException || !getServer().isRunning())
{
- // Handle exception via AsyncListener onError
- Throwable root = _state.getAsyncContextEvent().getThrowable();
- if (root==null)
- {
- _state.error(x);
- }
+ if (LOG.isDebugEnabled())
+ LOG.debug(_request.getRequestURI(), failure);
+ }
+ else if (failure instanceof BadMessageException)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.warn(_request.getRequestURI(), failure);
else
- {
- // TODO Can this happen? Should this just be ISE???
- // We've already processed an error before!
- root.addSuppressed(x);
- LOG.warn("Error while handling async error: ", root);
- abort(x);
- _state.errorComplete();
- }
+ LOG.warn("{} {}",_request.getRequestURI(), failure.getMessage());
}
else
+ {
+ LOG.info(_request.getRequestURI(), failure);
+ }
+
+ try
{
try
{
- // Handle error normally
- _request.setHandled(true);
- _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, x);
- _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, x.getClass());
-
- if (isCommitted())
+ _state.onError(failure);
+ }
+ catch (Exception e)
+ {
+ LOG.warn(e);
+ // Error could not be handled, probably due to error thrown from error dispatch
+ if (_response.isCommitted())
{
- abort(x);
- if (LOG.isDebugEnabled())
- LOG.debug("Could not send response error 500, already committed", x);
+ LOG.warn("ERROR Dispatch failed: ",failure);
+ _transport.abort(failure);
}
else
{
- _response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
-
- if (x instanceof BadMessageException)
- {
- BadMessageException bme = (BadMessageException)x;
- _response.sendError(bme.getCode(), bme.getReason());
- }
- else if (x instanceof UnavailableException)
- {
- if (((UnavailableException)x).isPermanent())
- _response.sendError(HttpStatus.NOT_FOUND_404);
- else
- _response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
- }
- else
- _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ // Minimal response
+ Integer code=(Integer)_request.getAttribute(ERROR_STATUS_CODE);
+ _response.reset();
+ _response.setStatus(code==null?500:code.intValue());
+ _response.flushBuffer();
}
}
- catch (Throwable e)
- {
- abort(e);
- if (LOG.isDebugEnabled())
- LOG.debug("Could not commit response error 500", e);
- }
+ }
+ catch(Exception e)
+ {
+ failure.addSuppressed(e);
+ LOG.warn("ERROR Dispatch failed: ",failure);
+ _transport.abort(failure);
}
}
@@ -550,8 +525,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
_requests,
_committed.get(),
_state.getState(),
- _state.getState()==HttpChannelState.State.IDLE?"-":_request.getRequestURI()
- );
+ _request.getHttpURI());
}
public void onRequest(MetaData.Request request)
@@ -609,7 +583,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (handler!=null)
content=handler.badMessageError(status,reason,fields);
- sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,status,reason,fields,0),content ,true);
+ sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,status,reason,fields,BufferUtil.length(content)),content ,true);
}
}
catch (IOException e)
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 8204b8b7e17..c757ca381f3 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
@@ -324,14 +324,14 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
case HTTP_2:
{
- // Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2, but not protocol negotiation
+ // Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2c.
_upgrade=PREAMBLE_UPGRADE_H2C;
if (HttpMethod.PRI.is(_metadata.getMethod()) &&
"*".equals(_metadata.getURI().toString()) &&
_fields.size()==0 &&
upgrade())
- return false;
+ return true;
badMessage(HttpStatus.UPGRADE_REQUIRED_426,null);
return false;
@@ -370,7 +370,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl
if (LOG.isDebugEnabled())
LOG.debug("upgrade {} {}",this,_upgrade);
- if (_upgrade!=PREAMBLE_UPGRADE_H2C && (_connection==null || !_connection.getValue().contains("Upgrade")))
+ if (_upgrade!=PREAMBLE_UPGRADE_H2C && (_connection==null || !_connection.contains("upgrade")))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
// Find the upgrade factory
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
index bf4af4b46ef..2c16602ec41 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
@@ -18,16 +18,23 @@
package org.eclipse.jetty.server;
+import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
+import static javax.servlet.RequestDispatcher.ERROR_MESSAGE;
+import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletResponse;
+import javax.servlet.UnavailableException;
+import org.eclipse.jetty.http.BadMessageException;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.util.log.Log;
@@ -45,12 +52,13 @@ public class HttpChannelState
private final static long DEFAULT_TIMEOUT=Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT",30000L);
/**
- * The dispatched state of the HttpChannel, used to control the overall lifecycle
+ * The state of the HttpChannel,used to control the overall lifecycle.
*/
public enum State
{
IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet
+ THROWN, // Exception thrown while DISPATCHED
ASYNC_WAIT, // Suspended and waiting
ASYNC_WOKEN, // Dispatch to handle from ASYNC_WAIT
ASYNC_IO, // Dispatched for async IO
@@ -67,7 +75,6 @@ public class HttpChannelState
DISPATCH, // handle a normal request dispatch
ASYNC_DISPATCH, // handle an async request dispatch
ERROR_DISPATCH, // handle a normal error
- ASYNC_ERROR, // handle an async error
WRITE_CALLBACK, // handle an IO write callback
READ_CALLBACK, // handle an IO read callback
COMPLETE, // Complete the response
@@ -76,14 +83,12 @@ public class HttpChannelState
}
/**
- * The state of the servlet async API. This can lead or follow the
- * channel dispatch state and also includes reasons such as expired,
- * dispatched or completed.
+ * The state of the servlet async API.
*/
public enum Async
{
STARTED, // AsyncContext.startAsync() has been called
- DISPATCH, //
+ DISPATCH, // AsyncContext.dispatch() has been called
COMPLETE, // AsyncContext.complete() has been called
EXPIRING, // AsyncContext timeout just happened
EXPIRED, // AsyncContext timeout has been processed
@@ -160,12 +165,18 @@ public class HttpChannelState
{
try(Locker.Lock lock= _locker.lock())
{
- return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial,
- _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"),
- _asyncWrite);
+ return toStringLocked();
}
}
+ public String toStringLocked()
+ {
+ return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial,
+ _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"),
+ _asyncWrite);
+ }
+
+
private String getStatusStringLocked()
{
return String.format("s=%s i=%b a=%s",_state,_initial,_async);
@@ -184,10 +195,11 @@ public class HttpChannelState
*/
protected Action handling()
{
- if(DEBUG)
- LOG.debug("{} handling {}",this,_state);
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("handling {}",toStringLocked());
+
switch(_state)
{
case IDLE:
@@ -228,17 +240,15 @@ public class HttpChannelState
_state=State.DISPATCHED;
_async=null;
return Action.ASYNC_DISPATCH;
- case EXPIRING:
- break;
case EXPIRED:
+ case ERRORED:
_state=State.DISPATCHED;
_async=null;
return Action.ERROR_DISPATCH;
case STARTED:
- return Action.WAIT;
+ case EXPIRING:
case ERRORING:
- _state=State.DISPATCHED;
- return Action.ASYNC_ERROR;
+ return Action.WAIT;
default:
throw new IllegalStateException(getStatusStringLocked());
@@ -264,45 +274,53 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("startAsync {}",toStringLocked());
+
if (_state!=State.DISPATCHED || _async!=null)
throw new IllegalStateException(this.getStatusStringLocked());
_async=Async.STARTED;
_event=event;
lastAsyncListeners=_asyncListeners;
- _asyncListeners=null;
+ _asyncListeners=null;
}
if (lastAsyncListeners!=null)
{
- for (AsyncListener listener : lastAsyncListeners)
+ Runnable callback=new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onStartAsync(event);
+ for (AsyncListener listener : lastAsyncListeners)
+ {
+ try
+ {
+ listener.onStartAsync(event);
+ }
+ catch(Exception e)
+ {
+ // TODO Async Dispatch Error
+ LOG.warn(e);
+ }
+ }
}
- catch(Exception e)
+ @Override
+ public String toString()
{
- // TODO Async Dispatch Error
- LOG.warn(e);
+ return "startAsync";
}
- }
+ };
+
+ runInContext(event,callback);
}
}
- protected void error(Throwable th)
- {
- try(Locker.Lock lock= _locker.lock())
- {
- if (_event!=null)
- _event.addThrowable(th);
- _async=Async.ERRORING;
- }
- }
/**
* Signal that the HttpConnection has finished handling the request.
- * For blocking connectors, this call may block if the request has
+ * For blocking connectors,this call may block if the request has
* been suspended (startAsync called).
* @return next actions
* be handled again (eg because of a resume that happened before unhandle was called)
@@ -313,17 +331,21 @@ public class HttpChannelState
AsyncContextEvent schedule_event=null;
boolean read_interested=false;
- if(DEBUG)
- LOG.debug("{} unhandle {}",this,_state);
-
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("unhandle {}",toStringLocked());
+
switch(_state)
{
case COMPLETING:
case COMPLETED:
return Action.TERMINATED;
+ case THROWN:
+ _state=State.DISPATCHED;
+ return Action.ERROR_DISPATCH;
+
case DISPATCHED:
case ASYNC_IO:
break;
@@ -349,12 +371,6 @@ public class HttpChannelState
action=Action.ASYNC_DISPATCH;
break;
- case EXPIRED:
- _state=State.DISPATCHED;
- _async=null;
- action = Action.ERROR_DISPATCH;
- break;
-
case STARTED:
if (_asyncReadUnready && _asyncReadPossible)
{
@@ -378,26 +394,27 @@ public class HttpChannelState
break;
case EXPIRING:
- schedule_event=_event;
+ // onTimeout callbacks still being called, so just WAIT
_state=State.ASYNC_WAIT;
action=Action.WAIT;
break;
- case ERRORING:
+ case EXPIRED:
+ // onTimeout handling is complete, but did not dispatch as
+ // we were handling. So do the error dispatch here
_state=State.DISPATCHED;
- action=Action.ASYNC_ERROR;
+ _async=null;
+ action=Action.ERROR_DISPATCH;
break;
-
+
case ERRORED:
_state=State.DISPATCHED;
- action=Action.ERROR_DISPATCH;
_async=null;
+ action=Action.ERROR_DISPATCH;
break;
default:
- _state=State.COMPLETING;
- action=Action.COMPLETE;
- break;
+ throw new IllegalStateException(this.getStatusStringLocked());
}
}
else
@@ -416,13 +433,22 @@ public class HttpChannelState
public void dispatch(ServletContext context, String path)
{
- boolean dispatch;
+ boolean dispatch=false;
+ AsyncContextEvent event;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("dispatch {} -> {}",toStringLocked(),path);
+
+ boolean started=false;
+ event=_event;
switch(_async)
{
case STARTED:
+ started=true;
+ break;
case EXPIRING:
+ case ERRORING:
case ERRORED:
break;
default:
@@ -435,27 +461,26 @@ public class HttpChannelState
if (path!=null)
_event.setDispatchPath(path);
- switch(_state)
+ if (started)
{
- case DISPATCHED:
- case ASYNC_IO:
- dispatch=false;
- break;
- case ASYNC_WAIT:
- _state=State.ASYNC_WOKEN;
- dispatch=true;
- break;
- case ASYNC_WOKEN:
- dispatch=false;
- break;
- default:
- LOG.warn("async dispatched when complete {}",this);
- dispatch=false;
- break;
+ switch(_state)
+ {
+ case DISPATCHED:
+ case ASYNC_IO:
+ case ASYNC_WOKEN:
+ break;
+ case ASYNC_WAIT:
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ break;
+ default:
+ LOG.warn("async dispatched when complete {}",this);
+ break;
+ }
}
}
- cancelTimeout();
+ cancelTimeout(event);
if (dispatch)
scheduleDispatch();
}
@@ -466,58 +491,88 @@ public class HttpChannelState
AsyncContextEvent event;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onTimeout {}",toStringLocked());
+
if (_async!=Async.STARTED)
return;
_async=Async.EXPIRING;
event=_event;
listeners=_asyncListeners;
+
}
- if (LOG.isDebugEnabled())
- LOG.debug("Async timeout {}",this);
-
+ final AtomicReference error=new AtomicReference();
if (listeners!=null)
{
- for (AsyncListener listener : listeners)
+ Runnable task=new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onTimeout(event);
+ for (AsyncListener listener : listeners)
+ {
+ try
+ {
+ listener.onTimeout(event);
+ }
+ catch(Throwable x)
+ {
+ LOG.debug("Exception while invoking listener " + listener,x);
+ if (error.get()==null)
+ error.set(x);
+ else
+ error.get().addSuppressed(x);
+ }
+ }
}
- catch(Exception e)
+ @Override
+ public String toString()
{
- LOG.debug(e);
- event.addThrowable(e);
- _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
- break;
+ return "onTimeout";
}
- }
+ };
+
+ runInContext(event,task);
}
+ Throwable th=error.get();
boolean dispatch=false;
try(Locker.Lock lock= _locker.lock())
{
- if (_async==Async.EXPIRING)
+ switch(_async)
{
- // If the listeners did not call dispatch() or complete(),
- // then the container must generate an error.
- if (event.getThrowable()==null)
- {
- _async=Async.EXPIRED;
- _event.addThrowable(new TimeoutException("Async API violation"));
- }
- else
- {
- _async=Async.ERRORING;
- }
- if (_state==State.ASYNC_WAIT)
- {
- _state=State.ASYNC_WOKEN;
- dispatch=true;
- }
+ case EXPIRING:
+ _async=th==null ? Async.EXPIRED : Async.ERRORING;
+ break;
+
+ case COMPLETE:
+ case DISPATCH:
+ if (th!=null)
+ {
+ LOG.ignore(th);
+ th=null;
+ }
+ break;
+
+ default:
+ throw new IllegalStateException();
+ }
+
+ if (_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
}
}
+ if (th!=null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Error after async timeout {}",this,th);
+ onError(th);
+ }
+
if (dispatch)
{
if (LOG.isDebugEnabled())
@@ -528,42 +583,53 @@ public class HttpChannelState
public void complete()
{
+
// just like resume, except don't set _dispatched=true;
boolean handle=false;
+ AsyncContextEvent event;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("complete {}",toStringLocked());
+
+ boolean started=false;
+ event=_event;
+
switch(_async)
{
case STARTED:
+ started=true;
+ break;
case EXPIRING:
+ case ERRORING:
case ERRORED:
break;
+ case COMPLETE:
+ return;
default:
throw new IllegalStateException(this.getStatusStringLocked());
}
_async=Async.COMPLETE;
- if (_state==State.ASYNC_WAIT)
+
+ if (started && _state==State.ASYNC_WAIT)
{
handle=true;
_state=State.ASYNC_WOKEN;
}
}
- cancelTimeout();
+ cancelTimeout(event);
if (handle)
- {
- ContextHandler handler=getContextHandler();
- if (handler!=null)
- handler.handle(_channel.getRequest(),_channel);
- else
- _channel.handle();
- }
+ runInContext(event,_channel);
}
public void errorComplete()
{
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("error complete {}",toStringLocked());
+
_async=Async.COMPLETE;
_event.setDispatchContext(null);
_event.setDispatchPath(null);
@@ -571,41 +637,143 @@ public class HttpChannelState
cancelTimeout();
}
-
- protected void onError()
+
+ protected void onError(Throwable failure)
{
- final List aListeners;
+ final List listeners;
final AsyncContextEvent event;
-
+ final Request baseRequest = _channel.getRequest();
+
+ int code=HttpStatus.INTERNAL_SERVER_ERROR_500;
+ String reason=null;
+ if (failure instanceof BadMessageException)
+ {
+ BadMessageException bme = (BadMessageException)failure;
+ code = bme.getCode();
+ reason = bme.getReason();
+ }
+ else if (failure instanceof UnavailableException)
+ {
+ if (((UnavailableException)failure).isPermanent())
+ code = HttpStatus.NOT_FOUND_404;
+ else
+ code = HttpStatus.SERVICE_UNAVAILABLE_503;
+ }
+
try(Locker.Lock lock= _locker.lock())
{
- if (_state!=State.DISPATCHED/* || _async!=Async.ERRORING*/)
+ if(DEBUG)
+ LOG.debug("onError {} {}",toStringLocked(),failure);
+
+ // Set error on request.
+ if(_event!=null)
+ {
+ if (_event.getThrowable()!=null)
+ throw new IllegalStateException("Error already set",_event.getThrowable());
+ _event.addThrowable(failure);
+ _event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code);
+ _event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,failure);
+ _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass());
+
+ _event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,reason!=null?reason:null);
+ }
+ else
+ {
+ Throwable error = (Throwable)baseRequest.getAttribute(ERROR_EXCEPTION);
+ if (error!=null)
+ throw new IllegalStateException("Error already set",error);
+ baseRequest.setAttribute(ERROR_STATUS_CODE,code);
+ baseRequest.setAttribute(ERROR_EXCEPTION,failure);
+ baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass());
+ baseRequest.setAttribute(ERROR_MESSAGE,reason!=null?reason:null);
+ }
+
+ // Are we blocking?
+ if (_async==null)
+ {
+ // Only called from within HttpChannel Handling, so much be dispatched, let's stay dispatched!
+ if (_state==State.DISPATCHED)
+ {
+ _state=State.THROWN;
+ return;
+ }
throw new IllegalStateException(this.getStatusStringLocked());
-
- aListeners=_asyncListeners;
+ }
+
+ // We are Async
+ _async=Async.ERRORING;
+ listeners=_asyncListeners;
event=_event;
- _async=Async.ERRORED;
}
- if (event!=null && aListeners!=null)
+ if(listeners!=null)
{
- event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
- event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
- for (AsyncListener listener : aListeners)
+ Runnable task=new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onError(event);
+ for (AsyncListener listener : listeners)
+ {
+ try
+ {
+ listener.onError(event);
+ }
+ catch (Throwable x)
+ {
+ LOG.info("Exception while invoking listener " + listener,x);
+ }
+ }
}
- catch(Exception x)
+
+ @Override
+ public String toString()
{
- LOG.info("Exception while invoking listener " + listener, x);
+ return "onError";
+ }
+ };
+ runInContext(event,task);
+ }
+
+ boolean dispatch=false;
+ try(Locker.Lock lock= _locker.lock())
+ {
+ switch(_async)
+ {
+ case ERRORING:
+ {
+ // Still in this state ? The listeners did not invoke API methods
+ // and the container must provide a default error dispatch.
+ _async=Async.ERRORED;
+ break;
+ }
+ case DISPATCH:
+ case COMPLETE:
+ {
+ // The listeners called dispatch() or complete().
+ break;
+ }
+ default:
+ {
+ throw new IllegalStateException(toString());
}
}
+
+ if(_state==State.ASYNC_WAIT)
+ {
+ _state=State.ASYNC_WOKEN;
+ dispatch=true;
+ }
+ }
+
+ if(dispatch)
+ {
+ if(LOG.isDebugEnabled())
+ LOG.debug("Dispatch after error {}",this);
+ scheduleDispatch();
}
}
-
protected void onComplete()
{
final List aListeners;
@@ -613,6 +781,9 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onComplete {}",toStringLocked());
+
switch(_state)
{
case COMPLETING:
@@ -631,17 +802,31 @@ public class HttpChannelState
{
if (aListeners!=null)
{
- for (AsyncListener listener : aListeners)
+ Runnable callback = new Runnable()
{
- try
+ @Override
+ public void run()
{
- listener.onComplete(event);
- }
- catch(Exception e)
+ for (AsyncListener listener : aListeners)
+ {
+ try
+ {
+ listener.onComplete(event);
+ }
+ catch(Exception e)
+ {
+ LOG.warn("Exception while invoking listener " + listener,e);
+ }
+ }
+ }
+ @Override
+ public String toString()
{
- LOG.warn(e);
+ return "onComplete";
}
- }
+ };
+
+ runInContext(event,callback);
}
event.completed();
}
@@ -652,6 +837,9 @@ public class HttpChannelState
cancelTimeout();
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("recycle {}",toStringLocked());
+
switch(_state)
{
case DISPATCHED:
@@ -678,6 +866,9 @@ public class HttpChannelState
cancelTimeout();
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("upgrade {}",toStringLocked());
+
switch(_state)
{
case IDLE:
@@ -697,7 +888,6 @@ public class HttpChannelState
}
}
-
protected void scheduleDispatch()
{
_channel.execute(_channel);
@@ -717,10 +907,15 @@ public class HttpChannelState
{
event=_event;
}
+ cancelTimeout(event);
+ }
+
+ protected void cancelTimeout(AsyncContextEvent event)
+ {
if (event!=null)
event.cancelTimeoutTask();
}
-
+
public boolean isIdle()
{
try(Locker.Lock lock= _locker.lock())
@@ -779,7 +974,6 @@ public class HttpChannelState
}
}
-
public boolean isAsync()
{
try(Locker.Lock lock= _locker.lock())
@@ -805,7 +999,11 @@ public class HttpChannelState
{
event=_event;
}
+ return getContextHandler(event);
+ }
+ ContextHandler getContextHandler(AsyncContextEvent event)
+ {
if (event!=null)
{
Context context=((Context)event.getServletContext());
@@ -822,11 +1020,25 @@ public class HttpChannelState
{
event=_event;
}
+ return getServletResponse(event);
+ }
+
+ public ServletResponse getServletResponse(AsyncContextEvent event)
+ {
if (event!=null && event.getSuppliedResponse()!=null)
return event.getSuppliedResponse();
return _channel.getResponse();
}
-
+
+ void runInContext(AsyncContextEvent event,Runnable runnable)
+ {
+ ContextHandler contextHandler = getContextHandler(event);
+ if (contextHandler==null)
+ runnable.run();
+ else
+ contextHandler.handle(_channel.getRequest(),runnable);
+ }
+
public Object getAttribute(String name)
{
return _channel.getRequest().getAttribute(name);
@@ -855,6 +1067,9 @@ public class HttpChannelState
boolean interested=false;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onReadUnready {}",toStringLocked());
+
// We were already unready, this is not a state change, so do nothing
if (!_asyncReadUnready)
{
@@ -881,6 +1096,9 @@ public class HttpChannelState
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onReadPossible {}",toStringLocked());
+
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT && _asyncReadUnready)
{
@@ -903,6 +1121,9 @@ public class HttpChannelState
boolean woken=false;
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onReadReady {}",toStringLocked());
+
_asyncReadUnready=true;
_asyncReadPossible=true;
if (_state==State.ASYNC_WAIT)
@@ -928,6 +1149,9 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock())
{
+ if(DEBUG)
+ LOG.debug("onWritePossible {}",toStringLocked());
+
_asyncWrite=true;
if (_state==State.ASYNC_WAIT)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
index 4f3a84b1474..3496017ecdf 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
@@ -212,7 +212,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
public void onFillable()
{
if (LOG.isDebugEnabled())
- LOG.debug("{} onFillable enter {}", this, _channel.getState());
+ LOG.debug("{} onFillable enter {} {}", this, _channel.getState(),BufferUtil.toDetailString(_requestBuffer));
HttpConnection last=setCurrentConnection(this);
try
@@ -259,7 +259,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{
setCurrentConnection(last);
if (LOG.isDebugEnabled())
- LOG.debug("{} onFillable exit {}", this, _channel.getState());
+ LOG.debug("{} onFillable exit {} {}", this, _channel.getState(),BufferUtil.toDetailString(_requestBuffer));
}
}
@@ -272,8 +272,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
boolean handled=false;
while (_parser.inContentState())
{
- if (LOG.isDebugEnabled())
- LOG.debug("{} parseContent",this);
int filled = fillRequestBuffer();
boolean handle = parseRequestBuffer();
handled|=handle;
@@ -300,7 +298,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// No pretend we read -1
_parser.atEOF();
if (LOG.isDebugEnabled())
- LOG.debug("{} filled -1",this);
+ LOG.debug("{} filled -1 {}",this,BufferUtil.toDetailString(_requestBuffer));
return -1;
}
@@ -321,7 +319,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_parser.atEOF();
if (LOG.isDebugEnabled())
- LOG.debug("{} filled {}",this,filled);
+ LOG.debug("{} filled {} {}",this,filled,BufferUtil.toDetailString(_requestBuffer));
return filled;
}
@@ -517,6 +515,51 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return new Content(c);
}
+ @Override
+ public void abort(Throwable failure)
+ {
+ // Do a direct close of the output, as this may indicate to a client that the
+ // response is bad either with RST or by abnormal completion of chunked response.
+ getEndPoint().close();
+ }
+
+ @Override
+ public boolean isPushSupported()
+ {
+ return false;
+ }
+
+ @Override
+ public void push(org.eclipse.jetty.http.MetaData.Request request)
+ {
+ LOG.debug("ignore push in {}",this);
+ }
+
+ public void asyncReadFillInterested()
+ {
+ getEndPoint().fillInterested(_asyncReadCallback);
+ }
+
+ public void blockingReadFillInterested()
+ {
+ getEndPoint().fillInterested(_blockingReadCallback);
+ }
+
+ public void blockingReadException(Throwable e)
+ {
+ _blockingReadCallback.failed(e);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[p=%s,g=%s,c=%s]",
+ super.toString(),
+ _parser,
+ _generator,
+ _channel);
+ }
+
private class Content extends HttpInput.Content
{
public Content(ByteBuffer content)
@@ -646,24 +689,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
{
case NEED_HEADER:
{
- // Look for optimisation to avoid allocating a _header buffer
- /*
- Cannot use this optimisation unless we work out how not to overwrite data in user passed arrays.
- if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() )
- {
- // use spare space in content buffer for header buffer
- int p=_content.position();
- int l=_content.limit();
- _content.position(l);
- _content.limit(l+_config.getResponseHeaderSize());
- _header=_content.slice();
- _header.limit(0);
- _content.position(p);
- _content.limit(l);
- }
- else
- */
- _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
+ _header = _bufferPool.acquire(_config.getResponseHeaderSize(), HEADER_BUFFER_DIRECT);
continue;
}
@@ -764,48 +790,4 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
return String.format("%s[i=%s,cb=%s]",super.toString(),_info,_callback);
}
}
-
- @Override
- public void abort(Throwable failure)
- {
- // Do a direct close of the output, as this may indicate to a client that the
- // response is bad either with RST or by abnormal completion of chunked response.
- getEndPoint().close();
- }
-
- @Override
- public boolean isPushSupported()
- {
- return false;
- }
-
- /**
- * @see org.eclipse.jetty.server.HttpTransport#push(org.eclipse.jetty.http.MetaData.Request)
- */
- @Override
- public void push(org.eclipse.jetty.http.MetaData.Request request)
- {
- LOG.debug("ignore push in {}",this);
- }
-
- public void asyncReadFillInterested()
- {
- getEndPoint().fillInterested(_asyncReadCallback);
- }
-
- public void blockingReadFillInterested()
- {
- getEndPoint().fillInterested(_blockingReadCallback);
- }
-
- public void blockingReadException(Throwable e)
- {
- _blockingReadCallback.failed(e);
- }
-
- @Override
- public String toString()
- {
- return super.toString()+"<--"+BufferUtil.toDetailString(_requestBuffer);
- }
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
index 98b122ba879..f8b14734df7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
@@ -21,7 +21,9 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
import java.util.Objects;
+import java.util.Queue;
import java.util.concurrent.TimeoutException;
import javax.servlet.ReadListener;
@@ -29,7 +31,6 @@ import javax.servlet.ServletInputStream;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.io.RuntimeIOException;
-import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
@@ -48,9 +49,9 @@ public class HttpInput extends ServletInputStream implements Runnable
private final static Logger LOG = Log.getLogger(HttpInput.class);
private final static Content EOF_CONTENT = new EofContent("EOF");
private final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
-
+
private final byte[] _oneByteBuffer = new byte[1];
- private final ArrayQueue _inputQ = new ArrayQueue<>();
+ private final Queue _inputQ = new ArrayDeque<>();
private final HttpChannelState _channelState;
private ReadListener _listener;
private State _state = STREAM;
@@ -63,21 +64,21 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_channelState.getHttpChannel().getHttpConfiguration().getBlockingTimeout()>0)
_blockingTimeoutAt=0;
}
-
+
protected HttpChannelState getHttpChannelState()
{
return _channelState;
}
-
+
public void recycle()
{
synchronized (_inputQ)
{
- Content item = _inputQ.pollUnsafe();
+ Content item = _inputQ.poll();
while (item != null)
{
item.failed(null);
- item = _inputQ.pollUnsafe();
+ item = _inputQ.poll();
}
_listener = null;
_state = STREAM;
@@ -92,7 +93,7 @@ public class HttpInput extends ServletInputStream implements Runnable
boolean woken=false;
synchronized (_inputQ)
{
- Content content = _inputQ.peekUnsafe();
+ Content content = _inputQ.peek();
if (content==null)
{
try
@@ -103,13 +104,13 @@ public class HttpInput extends ServletInputStream implements Runnable
{
woken=failed(e);
}
- content = _inputQ.peekUnsafe();
+ content = _inputQ.peek();
}
-
+
if (content!=null)
available= remaining(content);
}
-
+
if (woken)
wake();
return available;
@@ -117,10 +118,10 @@ public class HttpInput extends ServletInputStream implements Runnable
private void wake()
{
- _channelState.getHttpChannel().getConnector().getExecutor().execute(_channelState.getHttpChannel());
+ _channelState.getHttpChannel().getConnector().getExecutor().execute(_channelState.getHttpChannel());
}
-
-
+
+
@Override
public int read() throws IOException
{
@@ -137,7 +138,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (_blockingTimeoutAt>=0 && !isAsync())
_blockingTimeoutAt=System.currentTimeMillis()+getHttpChannelState().getHttpChannel().getHttpConfiguration().getBlockingTimeout();
-
+
while(true)
{
Content item = nextContent();
@@ -146,12 +147,12 @@ public class HttpInput extends ServletInputStream implements Runnable
if (LOG.isDebugEnabled())
LOG.debug("{} read {} from {}",this,len,item);
int l = get(item, b, off, len);
-
+
consumeNonContent();
-
+
return l;
}
-
+
if (!_state.blockForContent(this))
return _state.noContent();
}
@@ -159,7 +160,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
/**
- * Called when derived implementations should attempt to
+ * Called when derived implementations should attempt to
* produce more Content and add it via {@link #addContent(Content)}.
* For protocols that are constantly producing (eg HTTP2) this can
* be left as a noop;
@@ -168,11 +169,11 @@ public class HttpInput extends ServletInputStream implements Runnable
protected void produceContent() throws IOException
{
}
-
+
/**
* Get the next content from the inputQ, calling {@link #produceContent()}
* if need be. EOF is processed and state changed.
- *
+ *
* @return the content or null if none available.
* @throws IOException if retrieving the content fails
*/
@@ -186,7 +187,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
return content;
}
-
+
/** Poll the inputQ for Content.
* Consumed buffers and {@link PoisonPillContent}s are removed and
* EOF state updated if need be.
@@ -195,11 +196,11 @@ public class HttpInput extends ServletInputStream implements Runnable
protected Content pollContent()
{
// Items are removed only when they are fully consumed.
- Content content = _inputQ.peekUnsafe();
+ Content content = _inputQ.peek();
// Skip consumed items at the head of the queue.
while (content != null && remaining(content) == 0)
{
- _inputQ.pollUnsafe();
+ _inputQ.poll();
content.succeeded();
if (LOG.isDebugEnabled())
LOG.debug("{} consumed {}", this, content);
@@ -212,45 +213,45 @@ public class HttpInput extends ServletInputStream implements Runnable
{
_state=AEOF;
boolean woken = _channelState.onReadReady(); // force callback?
- if (woken)
+ if (woken)
wake();
}
}
else if (content==EARLY_EOF_CONTENT)
_state=EARLY_EOF;
- content = _inputQ.peekUnsafe();
+ content = _inputQ.peek();
}
-
+
return content;
}
- /**
+ /**
*/
protected void consumeNonContent()
{
// Items are removed only when they are fully consumed.
- Content content = _inputQ.peekUnsafe();
+ Content content = _inputQ.peek();
// Skip consumed items at the head of the queue.
while (content != null && remaining(content) == 0)
{
// Defer EOF until read
if (content instanceof EofContent)
break;
-
+
// Consume all other empty content
- _inputQ.pollUnsafe();
+ _inputQ.poll();
content.succeeded();
if (LOG.isDebugEnabled())
LOG.debug("{} consumed {}", this, content);
- content = _inputQ.peekUnsafe();
- }
+ content = _inputQ.peek();
+ }
}
/**
* Get the next readable from the inputQ, calling {@link #produceContent()}
* if need be. EOF is NOT processed and state is not changed.
- *
+ *
* @return the content or EOF or null if none available.
* @throws IOException if retrieving the content fails
*/
@@ -273,22 +274,22 @@ public class HttpInput extends ServletInputStream implements Runnable
protected Content pollReadable()
{
// Items are removed only when they are fully consumed.
- Content content = _inputQ.peekUnsafe();
-
+ Content content = _inputQ.peek();
+
// Skip consumed items at the head of the queue except EOF
while (content != null)
{
if (content==EOF_CONTENT || content==EARLY_EOF_CONTENT || remaining(content)>0)
return content;
-
- _inputQ.pollUnsafe();
+
+ _inputQ.poll();
content.succeeded();
if (LOG.isDebugEnabled())
LOG.debug("{} consumed {}", this, content);
- content = _inputQ.peekUnsafe();
+ content = _inputQ.peek();
}
-
- return content;
+
+ return null;
}
/**
@@ -334,7 +335,7 @@ public class HttpInput extends ServletInputStream implements Runnable
pollContent(); // hungry succeed
}
-
+
/**
* Blocks until some content or some end-of-file event arrives.
*
@@ -358,7 +359,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_inputQ.wait(timeout);
else
_inputQ.wait();
-
+
if (_blockingTimeoutAt>0 && System.currentTimeMillis()>=_blockingTimeoutAt)
throw new TimeoutException();
}
@@ -367,7 +368,7 @@ public class HttpInput extends ServletInputStream implements Runnable
throw (IOException)new InterruptedIOException().initCause(e);
}
}
-
+
/**
* Adds some content to this input stream.
*
@@ -379,16 +380,16 @@ public class HttpInput extends ServletInputStream implements Runnable
boolean woken=false;
synchronized (_inputQ)
{
- _inputQ.addUnsafe(item);
+ _inputQ.offer(item);
if (LOG.isDebugEnabled())
LOG.debug("{} addContent {}", this, item);
-
+
if (_listener==null)
_inputQ.notify();
else
- woken=_channelState.onReadPossible();
+ woken=_channelState.onReadPossible();
}
-
+
return woken;
}
@@ -396,10 +397,10 @@ public class HttpInput extends ServletInputStream implements Runnable
{
synchronized (_inputQ)
{
- return _inputQ.sizeUnsafe()>0;
+ return _inputQ.size()>0;
}
}
-
+
public void unblock()
{
synchronized (_inputQ)
@@ -407,7 +408,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_inputQ.notify();
}
}
-
+
public long getContentConsumed()
{
synchronized (_inputQ)
@@ -450,7 +451,7 @@ public class HttpInput extends ServletInputStream implements Runnable
Content item = nextContent();
if (item == null)
break; // Let's not bother blocking
-
+
skip(item, remaining(item));
}
return isFinished() && !isError();
@@ -470,7 +471,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return _state instanceof ErrorState;
}
}
-
+
public boolean isAsync()
{
synchronized (_inputQ)
@@ -487,7 +488,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return _state instanceof EOFState;
}
}
-
+
@Override
public boolean isReady()
@@ -531,7 +532,7 @@ public class HttpInput extends ServletInputStream implements Runnable
_state = ASYNC;
_listener = readListener;
boolean content=nextContent()!=null;
-
+
if (content)
woken = _channelState.onReadReady();
else
@@ -556,8 +557,8 @@ public class HttpInput extends ServletInputStream implements Runnable
LOG.warn(x);
else
_state = new ErrorState(x);
-
- if (_listener==null)
+
+ if (_listener==null)
_inputQ.notify();
else
woken=_channelState.onReadPossible();
@@ -569,7 +570,7 @@ public class HttpInput extends ServletInputStream implements Runnable
/* ------------------------------------------------------------ */
/*
*
- * While this class is-a Runnable, it should never be dispatched in it's own thread. It is a
+ * While this class is-a Runnable, it should never be dispatched in it's own thread. It is a
* runnable only so that the calling thread can use {@link ContextHandler#handle(Runnable)}
* to setup classloaders etc.
*
@@ -585,7 +586,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
if (_state==EOF)
return;
-
+
if (_state==AEOF)
{
_state=EOF;
@@ -593,7 +594,7 @@ public class HttpInput extends ServletInputStream implements Runnable
}
listener = _listener;
- error = _state instanceof ErrorState?((ErrorState)_state).getError():null;
+ error = _state instanceof ErrorState?((ErrorState)_state).getError():null;
}
try
@@ -604,9 +605,13 @@ public class HttpInput extends ServletInputStream implements Runnable
listener.onError(error);
}
else if (aeof)
- listener.onAllDataRead();
- else if (error == null)
+ {
+ listener.onAllDataRead();
+ }
+ else
+ {
listener.onDataAvailable();
+ }
}
catch (Throwable e)
{
@@ -629,6 +634,16 @@ public class HttpInput extends ServletInputStream implements Runnable
}
}
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[c=%d,s=%s]",
+ getClass().getSimpleName(),
+ hashCode(),
+ _contentConsumed,
+ _state);
+ }
+
public static class PoisonPillContent extends Content
{
private final String _name;
@@ -637,14 +652,14 @@ public class HttpInput extends ServletInputStream implements Runnable
super(BufferUtil.EMPTY_BUFFER);
_name=name;
}
-
+
@Override
public String toString()
{
return _name;
}
}
-
+
public static class EofContent extends PoisonPillContent
{
EofContent(String name)
@@ -652,46 +667,46 @@ public class HttpInput extends ServletInputStream implements Runnable
super(name);
}
}
-
+
public static class Content implements Callback
{
private final ByteBuffer _content;
-
+
public Content(ByteBuffer content)
{
_content=content;
}
-
+
@Override
public boolean isNonBlocking()
{
return true;
}
-
-
+
+
public ByteBuffer getContent()
{
return _content;
}
-
+
public boolean hasContent()
{
return _content.hasRemaining();
}
-
+
public int remaining()
{
return _content.remaining();
}
-
+
@Override
public String toString()
{
return String.format("Content@%x{%s}",hashCode(),BufferUtil.toDetailString(_content));
}
}
-
-
+
+
protected static abstract class State
{
public boolean blockForContent(HttpInput in) throws IOException
@@ -708,7 +723,7 @@ public class HttpInput extends ServletInputStream implements Runnable
protected static class EOFState extends State
{
}
-
+
protected class ErrorState extends EOFState
{
final Throwable _error;
@@ -716,7 +731,7 @@ public class HttpInput extends ServletInputStream implements Runnable
{
_error=error;
}
-
+
public Throwable getError()
{
return _error;
@@ -767,7 +782,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return "ASYNC";
}
};
-
+
protected static final State EARLY_EOF = new EOFState()
{
@Override
@@ -791,7 +806,7 @@ public class HttpInput extends ServletInputStream implements Runnable
return "EOF";
}
};
-
+
protected static final State AEOF = new EOFState()
{
@Override
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
index ca359518892..e19a44b4d95 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
@@ -82,7 +82,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
setWriteListener() READY->owp ise ise ise ise ise
write() OPEN ise PENDING wpe wpe eof
flush() OPEN ise PENDING wpe wpe eof
- close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED
+ close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED
isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true
write completed - - - ASYNC READY->owp -
*/
@@ -196,10 +196,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return;
}
case UNREADY:
+ case PENDING:
{
- if (_state.compareAndSet(state,OutputState.ERROR))
- _writeListener.onError(_onError==null?new EofException("Async close"):_onError);
- break;
+ if (!_state.compareAndSet(state,OutputState.CLOSED))
+ break;
+ IOException ex = new IOException("Closed while Pending/Unready");
+ LOG.warn(ex.toString());
+ LOG.debug(ex);
+ _channel.abort(ex);
}
default:
{
@@ -286,6 +290,20 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return _state.get()==OutputState.CLOSED;
}
+ public boolean isAsync()
+ {
+ switch(_state.get())
+ {
+ case ASYNC:
+ case READY:
+ case PENDING:
+ case UNREADY:
+ return true;
+ default:
+ return false;
+ }
+ }
+
@Override
public void flush() throws IOException
{
@@ -307,6 +325,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
return;
case PENDING:
+ return;
+
case UNREADY:
throw new WritePendingException();
@@ -1252,4 +1272,5 @@ public class HttpOutput extends ServletOutputStream implements Runnable
super.onCompleteFailure(x);
}
}
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
index 7865c6b7f2d..b1c1618e63d 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java
@@ -196,28 +196,17 @@ public class LocalConnector extends AbstractConnector
getExecutor().execute(task);
}
- @Override
- public void close()
- {
- boolean wasOpen=isOpen();
- super.close();
- if (wasOpen)
- {
- getConnection().onClose();
- onClose();
- }
- }
-
@Override
public void onClose()
{
+ getConnection().onClose();
LocalConnector.this.onEndPointClosed(this);
super.onClose();
_closed.countDown();
}
@Override
- public void shutdownOutput()
+ public void doShutdownOutput()
{
super.shutdownOutput();
close();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
index cfd96c18080..e4ed0a2c592 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java
@@ -26,10 +26,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.NetworkTrafficListener;
import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint;
-import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -84,7 +84,7 @@ public class NetworkTrafficServerConnector extends ServerConnector
}
@Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners);
return endPoint;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
index b72cf5718d4..7a5e51cca13 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
@@ -19,17 +19,23 @@
package org.eclipse.jetty.server;
import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
+import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@@ -38,14 +44,17 @@ import org.eclipse.jetty.util.log.Logger;
/**
* ConnectionFactory for the PROXY Protocol.
*
This factory can be placed in front of any other connection factory
- * to process the proxy line before the normal protocol handling
+ * to process the proxy v1 or v2 line before the normal protocol handling
*
* @see http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
*/
public class ProxyConnectionFactory extends AbstractConnectionFactory
{
+ public static final String TLS_VERSION = "TLS_VERSION";
+
private static final Logger LOG = Log.getLogger(ProxyConnectionFactory.class);
private final String _next;
+ private int _maxProxyHeader=1024;
/* ------------------------------------------------------------ */
/** Proxy Connection Factory that uses the next ConnectionFactory
@@ -63,6 +72,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
_next=nextProtocol;
}
+ public int getMaxProxyHeader()
+ {
+ return _maxProxyHeader;
+ }
+
+ public void setMaxProxyHeader(int maxProxyHeader)
+ {
+ _maxProxyHeader = maxProxyHeader;
+ }
+
@Override
public Connection newConnection(Connector connector, EndPoint endp)
{
@@ -80,24 +99,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
}
}
- return new ProxyConnection(endp,connector,next);
+ return new ProxyProtocolV1orV2Connection(endp,connector,next);
}
-
- public static class ProxyConnection extends AbstractConnection
+
+ public class ProxyProtocolV1orV2Connection extends AbstractConnection
{
- // 0 1 2 3 4 5 6
- // 98765432109876543210987654321
- // PROXY P R.R.R.R L.L.L.L R Lrn
-
- private final int[] __size = {29,23,21,13,5,3,1};
private final Connector _connector;
private final String _next;
- private final StringBuilder _builder=new StringBuilder();
- private final String[] _field=new String[6];
- private int _fields;
- private int _length;
-
- protected ProxyConnection(EndPoint endp, Connector connector, String next)
+ private ByteBuffer _buffer = BufferUtil.allocate(16);
+
+ protected ProxyProtocolV1orV2Connection(EndPoint endp, Connector connector, String next)
{
super(endp,connector.getExecutor());
_connector=connector;
@@ -111,15 +122,138 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
fillInterested();
}
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ while(BufferUtil.space(_buffer)>0)
+ {
+ // Read data
+ int fill=getEndPoint().fill(_buffer);
+ if (fill<0)
+ {
+ getEndPoint().shutdownOutput();
+ return;
+ }
+ if (fill==0)
+ {
+ fillInterested();
+ return;
+ }
+ }
+
+ // Is it a V1?
+ switch(_buffer.get(0))
+ {
+ case 'P':
+ {
+ ProxyProtocolV1Connection v1 = new ProxyProtocolV1Connection(getEndPoint(),_connector,_next,_buffer);
+ getEndPoint().upgrade(v1);
+ return;
+ }
+ case 0x0D:
+ {
+ ProxyProtocolV2Connection v2 = new ProxyProtocolV2Connection(getEndPoint(),_connector,_next,_buffer);
+ getEndPoint().upgrade(v2);
+ return;
+ }
+ default:
+ LOG.warn("Not PROXY protocol for {}",getEndPoint());
+ close();
+ }
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("PROXY error for "+getEndPoint(),x);
+ close();
+ }
+ }
+ }
+
+ public static class ProxyProtocolV1Connection extends AbstractConnection
+ {
+ // 0 1 2 3 4 5 6
+ // 98765432109876543210987654321
+ // PROXY P R.R.R.R L.L.L.L R Lrn
+
+ private final int[] __size = {29,23,21,13,5,3,1};
+ private final Connector _connector;
+ private final String _next;
+ private final StringBuilder _builder=new StringBuilder();
+ private final String[] _field=new String[6];
+ private int _fields;
+ private int _length;
+
+ protected ProxyProtocolV1Connection(EndPoint endp, Connector connector, String next,ByteBuffer buffer)
+ {
+ super(endp,connector.getExecutor());
+ _connector=connector;
+ _next=next;
+ _length=buffer.remaining();
+ parse(buffer);
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ fillInterested();
+ }
+
+
+ private boolean parse(ByteBuffer buffer)
+ {
+ // parse fields
+ while (buffer.hasRemaining())
+ {
+ byte b = buffer.get();
+ if (_fields<6)
+ {
+ if (b==' ' || b=='\r' && _fields==5)
+ {
+ _field[_fields++]=_builder.toString();
+ _builder.setLength(0);
+ }
+ else if (b<' ')
+ {
+ LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint());
+ close();
+ return false;
+ }
+ else
+ {
+ _builder.append((char)b);
+ }
+ }
+ else
+ {
+ if (b=='\n')
+ {
+ _fields=7;
+ return true;
+ }
+
+ LOG.warn("Bad CRLF for {}",getEndPoint());
+ close();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
@Override
public void onFillable()
{
try
{
ByteBuffer buffer=null;
- loop: while(true)
+ while(_fields<7)
{
// Create a buffer that will not read too much data
+ // since once read it is impossible to push back for the
+ // real connection to read it.
int size=Math.max(1,__size[_fields]-_builder.length());
if (buffer==null || buffer.capacity()!=size)
buffer=BufferUtil.allocate(size);
@@ -147,38 +281,8 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
return;
}
- // parse fields
- while (buffer.hasRemaining())
- {
- byte b = buffer.get();
- if (_fields<6)
- {
- if (b==' ' || b=='\r' && _fields==5)
- {
- _field[_fields++]=_builder.toString();
- _builder.setLength(0);
- }
- else if (b<' ')
- {
- LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint());
- close();
- return;
- }
- else
- {
- _builder.append((char)b);
- }
- }
- else
- {
- if (b=='\n')
- break loop;
-
- LOG.warn("Bad CRLF for {}",getEndPoint());
- close();
- return;
- }
- }
+ if (!parse(buffer))
+ return;
}
// Check proxy
@@ -197,10 +301,13 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next);
if (connectionFactory == null)
{
- LOG.info("Next protocol '{}' for {}",_next,getEndPoint());
+ LOG.warn("No Next protocol '{}' for {}",_next,getEndPoint());
close();
return;
}
+
+ if (LOG.isDebugEnabled())
+ LOG.warn("Next protocol '{}' for {} r={} l={}",_next,getEndPoint(),remote,local);
EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local);
Connection newConnection = connectionFactory.newConnection(_connector, endPoint);
@@ -213,8 +320,260 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
}
}
}
+
+
+ enum Family { UNSPEC, INET, INET6, UNIX };
+ enum Transport { UNSPEC, STREAM, DGRAM };
+ private static final byte[] MAGIC = new byte[]{0x0D,0x0A,0x0D,0x0A,0x00,0x0D,0x0A,0x51,0x55,0x49,0x54,0x0A};
+
+ public class ProxyProtocolV2Connection extends AbstractConnection
+ {
+ private final Connector _connector;
+ private final String _next;
+ private final boolean _local;
+ private final Family _family;
+ private final Transport _transport;
+ private final int _length;
+ private final ByteBuffer _buffer;
- public static class ProxyEndPoint implements EndPoint
+ protected ProxyProtocolV2Connection(EndPoint endp, Connector connector, String next,ByteBuffer buffer)
+ throws IOException
+ {
+ super(endp,connector.getExecutor());
+ _connector=connector;
+ _next=next;
+
+ if (buffer.remaining()!=16)
+ throw new IllegalStateException();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("PROXYv2 header {} for {}",BufferUtil.toHexSummary(buffer),this);
+
+ // struct proxy_hdr_v2 {
+ // uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
+ // uint8_t ver_cmd; /* protocol version and command */
+ // uint8_t fam; /* protocol family and address */
+ // uint16_t len; /* number of following bytes part of the header */
+ // };
+ for (int i=0;i>4)
+ {
+ case 0: _family=Family.UNSPEC; break;
+ case 1: _family=Family.INET; break;
+ case 2: _family=Family.INET6; break;
+ case 3: _family=Family.UNIX; break;
+ default:
+ throw new IOException("Bad PROXY protocol v2 family");
+ }
+
+ switch(0xf&transportAndFamily)
+ {
+ case 0: _transport=Transport.UNSPEC; break;
+ case 1: _transport=Transport.STREAM; break;
+ case 2: _transport=Transport.DGRAM; break;
+ default:
+ throw new IOException("Bad PROXY protocol v2 family");
+ }
+
+ _length = buffer.getChar();
+
+ if (!_local && (_family==Family.UNSPEC || _family==Family.UNIX || _transport!=Transport.STREAM))
+ throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x",versionAndCommand,transportAndFamily));
+
+ if (_length>_maxProxyHeader)
+ throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x,0x%x",versionAndCommand,transportAndFamily,_length));
+
+ _buffer = _length>0?BufferUtil.allocate(_length):BufferUtil.EMPTY_BUFFER;
+ }
+
+ @Override
+ public void onOpen()
+ {
+ super.onOpen();
+ if (_buffer.remaining()==_length)
+ next();
+ else
+ fillInterested();
+ }
+
+ @Override
+ public void onFillable()
+ {
+ try
+ {
+ while(_buffer.remaining()<_length)
+ {
+ // Read data
+ int fill=getEndPoint().fill(_buffer);
+ if (fill<0)
+ {
+ getEndPoint().shutdownOutput();
+ return;
+ }
+ if (fill==0)
+ {
+ fillInterested();
+ return;
+ }
+ }
+ }
+ catch (Throwable x)
+ {
+ LOG.warn("PROXY error for "+getEndPoint(),x);
+ close();
+ return;
+ }
+
+ next();
+ }
+
+ private void next()
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("PROXYv2 next {} from {} for {}",_next,BufferUtil.toHexSummary(_buffer),this);
+
+ // Create the next protocol
+ ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next);
+ if (connectionFactory == null)
+ {
+ LOG.info("Next protocol '{}' for {}",_next,getEndPoint());
+ close();
+ return;
+ }
+
+ // Do we need to wrap the endpoint?
+ EndPoint endPoint=getEndPoint();
+ if (!_local)
+ {
+ try
+ {
+ InetAddress src;
+ InetAddress dst;
+ int sp;
+ int dp;
+
+ switch(_family)
+ {
+ case INET:
+ {
+ byte[] addr=new byte[4];
+ _buffer.get(addr);
+ src = Inet4Address.getByAddress(addr);
+ _buffer.get(addr);
+ dst = Inet4Address.getByAddress(addr);
+ sp = _buffer.getChar();
+ dp = _buffer.getChar();
+
+ break;
+ }
+
+ case INET6:
+ {
+ byte[] addr=new byte[16];
+ _buffer.get(addr);
+ src = Inet6Address.getByAddress(addr);
+ _buffer.get(addr);
+ dst = Inet6Address.getByAddress(addr);
+ sp = _buffer.getChar();
+ dp = _buffer.getChar();
+ break;
+ }
+
+ default:
+ throw new IllegalStateException();
+ }
+
+
+ // Extract Addresses
+ InetSocketAddress remote=new InetSocketAddress(src,sp);
+ InetSocketAddress local =new InetSocketAddress(dst,dp);
+ ProxyEndPoint proxyEndPoint = new ProxyEndPoint(endPoint,remote,local);
+ endPoint = proxyEndPoint;
+
+
+ // Any additional info?
+ while(_buffer.hasRemaining())
+ {
+ int type = 0xff & _buffer.get();
+ int length = _buffer.getShort();
+ byte[] value = new byte[length];
+ _buffer.get(value);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(String.format("T=%x L=%d V=%s for %s",type,length,TypeUtil.toHexString(value),this));
+
+ // TODO interpret these values
+ switch(type)
+ {
+ case 0x01: // PP2_TYPE_ALPN
+ break;
+ case 0x02: // PP2_TYPE_AUTHORITY
+ break;
+ case 0x20: // PP2_TYPE_SSL
+ {
+ int i=0;
+ int client = 0xff & value[i++];
+ int verify = (0xff & value[i++])<<24 + (0xff & value[i++])<<16 + (0xff & value[i++])<<8 + (0xff&value[i++]);
+ while(i
- * A PushBuilder is obtained by calling {@link Request#getPushBuilder()}
- * which creates an initializes the builder as follows:
+ *
+ *
A PushBuilder is obtained by calling {@link
+ * Request#getPushBuilder()} (Eventually HttpServletRequest.getPushBuilder()).
+ * Each call to this method will
+ * return a new instance of a PushBuilder based off the current {@code
+ * HttpServletRequest}. Any mutations to the returned PushBuilder are
+ * not reflected on future returns.
+ *
+ *
The instance is initialized as follows:
+ *
*
- *
Each call to getPushBuilder() will return a new instance of a
- * PushBuilder based off the Request. Any mutations to the
- * returned PushBuilder are not reflected on future returns.
+ *
*
The method is initialized to "GET"
- *
The requests headers are added to the Builder, except for:
+ *
+ *
The existing headers of the current {@link HttpServletRequest}
+ * are added to the builder, except for:
+ *
+ *
*
Conditional headers (eg. If-Modified-Since)
*
Range headers
*
Expect headers
*
Authorization headers
*
Referrer headers
- *
- *
If the request was Authenticated, an Authorization header will
+ *
+ *
+ *
+ *
+ *
If the request was authenticated, an Authorization header will
* be set with a container generated token that will result in equivalent
- * Authorization for the pushed request
- *
The query string from {@link HttpServletRequest#getQueryString()}
- *
The {@link HttpServletRequest#getRequestedSessionId()} value, unless at the time
- * of the call {@link HttpServletRequest#getSession(boolean)}
- * has previously been called to create a new {@link HttpSession}, in
- * which case the new session ID will be used as the PushBuilders
- * requested session ID. The source of the requested session id will be the
- * same as for the request
- *
The Referer header will be set to {@link HttpServletRequest#getRequestURL()}
- * plus any {@link HttpServletRequest#getQueryString()}
+ * Authorization for the pushed request.
+ *
+ *
The {@link HttpServletRequest#getRequestedSessionId()} value,
+ * unless at the time of the call {@link
+ * HttpServletRequest#getSession(boolean)} has previously been called to
+ * create a new {@link HttpSession}, in which case the new session ID
+ * will be used as the PushBuilder's requested session ID. The source of
+ * the requested session id will be the same as for the request
+ *
+ *
The Referer(sic) header will be set to {@link
+ * HttpServletRequest#getRequestURL()} plus any {@link
+ * HttpServletRequest#getQueryString()}
+ *
*
If {@link HttpServletResponse#addCookie(Cookie)} has been called
* on the associated response, then a corresponding Cookie header will be added
* to the PushBuilder, unless the {@link Cookie#getMaxAge()} is <=0, in which
* case the Cookie will be removed from the builder.
- *
If this request has has the conditional headers If-Modified-Since or
- * If-None-Match then the {@link #isConditional()} header is set to true.
- *
- *
A PushBuilder can be customized by chained calls to mutator methods before the
- * {@link #push()} method is called to initiate a push request with the current state
- * of the builder. After the call to {@link #push()}, the builder may be reused for
- * another push, however the {@link #path(String)}, {@link #etag(String)} and
- * {@link #lastModified(String)} values will have been nulled. All other
- * values are retained over calls to {@link #push()}.
+ *
+ *
If this request has has the conditional headers If-Modified-Since
+ * or If-None-Match, then the {@link #isConditional()} header is set to
+ * true.
+ *
+ *
+ *
+ *
The {@link #path} method must be called on the {@code PushBuilder}
+ * instance before the call to {@link #push}. Failure to do so must
+ * cause an exception to be thrown from {@link
+ * #push}, as specified in that method.
+ *
+ *
A PushBuilder can be customized by chained calls to mutator
+ * methods before the {@link #push()} method is called to initiate an
+ * asynchronous push request with the current state of the builder.
+ * After the call to {@link #push()}, the builder may be reused for
+ * another push, however the implementation must make it so the {@link
+ * #path(String)}, {@link #etag(String)} and {@link
+ * #lastModified(String)} values are cleared before returning from
+ * {@link #push}. All other values are retained over calls to {@link
+ * #push()}.
+ *
+ * @since 4.0
*/
public interface PushBuilder
{
- /** Set the method to be used for the push.
- * Defaults to GET.
+ /**
+ *
Set the method to be used for the push.
+ *
+ *
Any non-empty String may be used for the method.
+ *
* @param method the method to be used for the push.
* @return this builder.
+ * @throws NullPointerException if the argument is {@code null}
+ * @throws IllegalArgumentException if the argument is the empty String
*/
public abstract PushBuilder method(String method);
/** Set the query string to be used for the push.
- * Defaults to the requests query string.
- * Will be appended to any query String included in a call to {@link #path(String)}. This
- * method should be used instead of a query in {@link #path(String)} when multiple
- * {@link #push()} calls are to be made with the same query string, or to remove a
- * query string obtained from the associated request.
+ *
+ * Will be appended to any query String included in a call to {@link
+ * #path(String)}. Any duplicate parameters must be preserved. This
+ * method should be used instead of a query in {@link #path(String)}
+ * when multiple {@link #push()} calls are to be made with the same
+ * query string.
* @param queryString the query string to be used for the push.
* @return this builder.
*/
@@ -108,33 +144,55 @@ public interface PushBuilder
*/
public abstract PushBuilder conditional(boolean conditional);
- /** Set a header to be used for the push.
+ /**
+ *
Set a header to be used for the push. If the builder has an
+ * existing header with the same name, its value is overwritten.
+ *
* @param name The header name to set
* @param value The header value to set
* @return this builder.
*/
public abstract PushBuilder setHeader(String name, String value);
+
- /** Add a header to be used for the push.
+ /**
+ *
Add a header to be used for the push.
* @param name The header name to add
* @param value The header value to add
* @return this builder.
*/
public abstract PushBuilder addHeader(String name, String value);
+
+
+ /**
+ *
Remove the named header. If the header does not exist, take
+ * no action.
+ *
+ * @param name The name of the header to remove
+ * @return this builder.
+ */
+ public abstract PushBuilder removeHeader(String name);
+
- /** Set the URI path to be used for the push.
- * The path may start with "/" in which case it is treated as an
- * absolute path, otherwise it is relative to the context path of
- * the associated request.
- * There is no path default and {@link #path(String)} must be called
- * before every call to {@link #push()}
+
+ /**
+ * Set the URI path to be used for the push. The path may start
+ * with "/" in which case it is treated as an absolute path,
+ * otherwise it is relative to the context path of the associated
+ * request. There is no path default and {@link #path(String)} must
+ * be called before every call to {@link #push()}. If a query
+ * string is present in the argument {@code path}, its contents must
+ * be merged with the contents previously passed to {@link
+ * #queryString}, preserving duplicates.
+ *
* @param path the URI path to be used for the push, which may include a
* query string.
* @return this builder.
*/
public abstract PushBuilder path(String path);
- /** Set the etag to be used for conditional pushes.
+ /**
+ * Set the etag to be used for conditional pushes.
* The etag will be used only if {@link #isConditional()} is true.
* Defaults to no etag. The value is nulled after every call to
* {@link #push()}
@@ -143,33 +201,44 @@ public interface PushBuilder
*/
public abstract PushBuilder etag(String etag);
- /** Set the last modified date to be used for conditional pushes.
- * The last modified date will be used only if {@link #isConditional()} is true.
- * Defaults to no date. The value is nulled after every call to
- * {@link #push()}
+ /**
+ * Set the last modified date to be used for conditional pushes.
+ * The last modified date will be used only if {@link
+ * #isConditional()} is true. Defaults to no date. The value is
+ * nulled after every call to {@link #push()}
* @param lastModified the last modified date to be used for the push.
* @return this builder.
- * */
+ */
public abstract PushBuilder lastModified(String lastModified);
- /** Push a resource.
- * Push a resource based on the current state of the PushBuilder. If {@link #isConditional()}
- * is true and an etag or lastModified value is provided, then an appropriate conditional header
- * will be generated. If both an etag and lastModified value are provided only an If-None-Match header
- * will be generated. If the builder has a session ID, then the pushed request
- * will include the session ID either as a Cookie or as a URI parameter as appropriate. The builders
- * query string is merged with any passed query string.
- * After initiating the push, the builder has its path, etag and lastModified fields nulled. All
- * other fields are left as is for possible reuse in another push.
- * @throws IllegalArgumentException if the method set expects a request body (eg POST)
+ /** Push a resource given the current state of the builder,
+ * returning immediately without blocking.
+ *
+ *
Push a resource based on the current state of the PushBuilder.
+ * If {@link #isConditional()} is true and an etag or lastModified
+ * value is provided, then an appropriate conditional header will be
+ * generated. If both an etag and lastModified value are provided
+ * only an If-None-Match header will be generated. If the builder
+ * has a session ID, then the pushed request will include the
+ * session ID either as a Cookie or as a URI parameter as
+ * appropriate. The builders query string is merged with any passed
+ * query string.
+ *
+ *
Before returning from this method, the builder has its path,
+ * etag and lastModified fields nulled. All other fields are left as
+ * is for possible reuse in another push.
+ *
+ * @throws IllegalArgumentException if the method set expects a
+ * request body (eg POST)
+ *
+ * @throws IllegalStateException if there was no call to {@link
+ * #path} on this instance either between its instantiation or the
+ * last call to {@code push()} that did not throw an
+ * IllegalStateException.
*/
public abstract void push();
-
-
-
-
public abstract String getMethod();
public abstract String getQueryString();
public abstract String getSessionId();
@@ -179,7 +248,4 @@ public interface PushBuilder
public abstract String getPath();
public abstract String getEtag();
public abstract String getLastModified();
-
-
-
}
\ No newline at end of file
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
index f39cffa878d..def6bed8880 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java
@@ -32,14 +32,14 @@ import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
-/**
+/**
*/
public class PushBuilderImpl implements PushBuilder
-{
+{
private static final Logger LOG = Log.getLogger(PushBuilderImpl.class);
private final static HttpField JettyPush = new HttpField("x-http2-push","PushBuilder");
-
+
private final Request _request;
private final HttpFields _fields;
private String _method;
@@ -49,7 +49,7 @@ public class PushBuilderImpl implements PushBuilder
private String _path;
private String _etag;
private String _lastModified;
-
+
public PushBuilderImpl(Request request, HttpFields fields, String method, String queryString, String sessionId, boolean conditional)
{
super();
@@ -65,124 +65,88 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getMethod()
- */
@Override
public String getMethod()
{
return _method;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#method(java.lang.String)
- */
@Override
public PushBuilder method(String method)
{
_method = method;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getQueryString()
- */
@Override
public String getQueryString()
{
return _queryString;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#queryString(java.lang.String)
- */
@Override
public PushBuilder queryString(String queryString)
{
_queryString = queryString;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getSessionId()
- */
@Override
public String getSessionId()
{
return _sessionId;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#sessionId(java.lang.String)
- */
@Override
public PushBuilder sessionId(String sessionId)
{
_sessionId = sessionId;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#isConditional()
- */
@Override
public boolean isConditional()
{
return _conditional;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#conditional(boolean)
- */
@Override
public PushBuilder conditional(boolean conditional)
{
_conditional = conditional;
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getHeaderNames()
- */
@Override
public Set getHeaderNames()
{
return _fields.getFieldNamesCollection();
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getHeader(java.lang.String)
- */
@Override
public String getHeader(String name)
{
return _fields.get(name);
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#setHeader(java.lang.String, java.lang.String)
- */
@Override
public PushBuilder setHeader(String name,String value)
{
_fields.put(name,value);
return this;
}
-
+
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#addHeader(java.lang.String, java.lang.String)
- */
@Override
public PushBuilder addHeader(String name,String value)
{
@@ -190,11 +154,15 @@ public class PushBuilderImpl implements PushBuilder
return this;
}
-
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getPath()
- */
+ @Override
+ public PushBuilder removeHeader(String name)
+ {
+ _fields.remove(name);
+ return this;
+ }
+
+ /* ------------------------------------------------------------ */
@Override
public String getPath()
{
@@ -202,9 +170,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#path(java.lang.String)
- */
@Override
public PushBuilder path(String path)
{
@@ -213,9 +178,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getEtag()
- */
@Override
public String getEtag()
{
@@ -223,9 +185,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#etag(java.lang.String)
- */
@Override
public PushBuilder etag(String etag)
{
@@ -234,9 +193,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#getLastModified()
- */
@Override
public String getLastModified()
{
@@ -244,9 +200,6 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#lastModified(java.lang.String)
- */
@Override
public PushBuilder lastModified(String lastModified)
{
@@ -255,40 +208,36 @@ public class PushBuilderImpl implements PushBuilder
}
/* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- /**
- * @see org.eclipse.jetty.server.PushBuilder#push()
- */
@Override
public void push()
{
if (HttpMethod.POST.is(_method) || HttpMethod.PUT.is(_method))
throw new IllegalStateException("Bad Method "+_method);
-
+
if (_path==null || _path.length()==0)
throw new IllegalStateException("Bad Path "+_path);
-
+
String path=_path;
String query=_queryString;
int q=path.indexOf('?');
if (q>=0)
{
- query=(query!=null && query.length()>0)?(_path.substring(q+1)+'&'+query):_path.substring(q+1);
- path=_path.substring(0,q);
+ query=(query!=null && query.length()>0)?(path.substring(q+1)+'&'+query):path.substring(q+1);
+ path=path.substring(0,q);
}
-
+
if (!path.startsWith("/"))
path=URIUtil.addPaths(_request.getContextPath(),path);
-
+
String param=null;
if (_sessionId!=null)
{
if (_request.isRequestedSessionIdFromURL())
param="jsessionid="+_sessionId;
- // TODO else
+ // TODO else
// _fields.add("Cookie","JSESSIONID="+_sessionId);
}
-
+
if (_conditional)
{
if (_etag!=null)
@@ -296,16 +245,17 @@ public class PushBuilderImpl implements PushBuilder
else if (_lastModified!=null)
_fields.add(HttpHeader.IF_MODIFIED_SINCE,_lastModified);
}
-
- HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),_path,param,query,null);
+
+ HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),path,param,query,null);
MetaData.Request push = new MetaData.Request(_method,uri,_request.getHttpVersion(),_fields);
-
+
if (LOG.isDebugEnabled())
LOG.debug("Push {} {} inm={} ims={}",_method,uri,_fields.get(HttpHeader.IF_NONE_MATCH),_fields.get(HttpHeader.IF_MODIFIED_SINCE));
-
+
_request.getHttpChannel().getHttpTransport().push(push);
_path=null;
_etag=null;
_lastModified=null;
}
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index 1f21e630890..ca6fdf6fb90 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -126,12 +126,12 @@ public class Request implements HttpServletRequest
private static final Logger LOG = Log.getLogger(Request.class);
private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
private static final int __NONE = 0, _STREAM = 1, __READER = 2;
-
+
private static final MultiMap NO_PARAMS = new MultiMap<>();
/* ------------------------------------------------------------ */
- /**
+ /**
* Obtain the base {@link Request} instance of a {@link ServletRequest}, by
* coercion, unwrapping or special attribute.
* @param request The request
@@ -141,31 +141,32 @@ public class Request implements HttpServletRequest
{
if (request instanceof Request)
return (Request)request;
-
+
Object channel = request.getAttribute(HttpChannel.class.getName());
if (channel instanceof HttpChannel)
return ((HttpChannel)channel).getRequest();
-
+
while (request instanceof ServletRequestWrapper)
request=((ServletRequestWrapper)request).getRequest();
if (request instanceof Request)
return (Request)request;
-
+
return null;
}
-
-
+
+
private final HttpChannel _channel;
private final List _requestAttributeListeners=new ArrayList<>();
private final HttpInput _input;
private MetaData.Request _metadata;
+ private String _originalURI;
private String _contextPath;
private String _servletPath;
private String _pathInfo;
-
+
private boolean _secure;
private boolean _asyncSupported = true;
private boolean _newContext;
@@ -195,7 +196,7 @@ public class Request implements HttpServletRequest
private long _timeStamp;
private MultiPartInputStreamParser _multiPartInputStream; //if the request is a multi-part mime
private AsyncContextState _async;
-
+
/* ------------------------------------------------------------ */
public Request(HttpChannel channel, HttpInput input)
{
@@ -226,7 +227,7 @@ public class Request implements HttpServletRequest
{
return getHttpChannel().getHttpTransport().isPushSupported();
}
-
+
/* ------------------------------------------------------------ */
/** Get a PushBuilder associated with this request initialized as follows:
*
The method is initialized to "GET"
@@ -237,18 +238,18 @@ public class Request implements HttpServletRequest
*
Authorization headers
*
Referrer headers
*
- *
If the request was Authenticated, an Authorization header will
+ *
If the request was Authenticated, an Authorization header will
* be set with a container generated token that will result in equivalent
* Authorization
*
The query string from {@link #getQueryString()}
*
The {@link #getRequestedSessionId()} value, unless at the time
* of the call {@link #getSession(boolean)}
- * has previously been called to create a new {@link HttpSession}, in
- * which case the new session ID will be used as the PushBuilders
+ * has previously been called to create a new {@link HttpSession}, in
+ * which case the new session ID will be used as the PushBuilders
* requested session ID.
*
The source of the requested session id will be the same as for
* this request
- *
The builders Referer header will be set to {@link #getRequestURL()}
+ *
The builders Referer header will be set to {@link #getRequestURL()}
* plus any {@link #getQueryString()}
*
If {@link HttpServletResponse#addCookie(Cookie)} has been called
* on the associated response, then a corresponding Cookie header will be added
@@ -256,9 +257,9 @@ public class Request implements HttpServletRequest
* case the Cookie will be removed from the builder.
*
If this request has has the conditional headers If-Modified-Since or
* If-None-Match then the {@link PushBuilderImpl#isConditional()} header is set
- * to true.
+ * to true.
*
- *
+ *
*
Each call to getPushBuilder() will return a new instance
* of a PushBuilder based off this Request. Any mutations to the
* returned PushBuilder are not reflected on future returns.
@@ -268,12 +269,12 @@ public class Request implements HttpServletRequest
{
if (!isPushSupported())
throw new IllegalStateException();
-
+
HttpFields fields = new HttpFields(getHttpFields().size()+5);
boolean conditional=false;
UserIdentity user_identity=null;
Authentication authentication=null;
-
+
for (HttpField field : getHttpFields())
{
HttpHeader header = field.getHeader();
@@ -291,7 +292,7 @@ public class Request implements HttpServletRequest
case REFERER:
case COOKIE:
continue;
-
+
case AUTHORIZATION:
user_identity=getUserIdentity();
authentication=_authentication;
@@ -301,13 +302,13 @@ public class Request implements HttpServletRequest
case IF_MODIFIED_SINCE:
conditional=true;
continue;
-
+
default:
fields.add(field);
}
}
}
-
+
String id=null;
try
{
@@ -324,13 +325,13 @@ public class Request implements HttpServletRequest
{
id=getRequestedSessionId();
}
-
+
PushBuilder builder = new PushBuilderImpl(this,fields,getMethod(),getQueryString(),id,conditional);
builder.addHeader("referer",getRequestURL().toString());
-
+
// TODO process any set cookies
// TODO process any user_identity
-
+
return builder;
}
@@ -418,7 +419,7 @@ public class Request implements HttpServletRequest
}
}
}
-
+
}
/* ------------------------------------------------------------ */
@@ -508,7 +509,7 @@ public class Request implements HttpServletRequest
HttpChannelState state = getHttpChannelState();
if (_async==null || !state.isAsyncStarted())
throw new IllegalStateException(state.getStatusString());
-
+
return _async;
}
@@ -647,7 +648,7 @@ public class Request implements HttpServletRequest
{
return _input.getContentConsumed();
}
-
+
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.ServletRequest#getContentType()
@@ -696,7 +697,7 @@ public class Request implements HttpServletRequest
{
if (_cookies == null || _cookies.getCookies().length == 0)
return null;
-
+
return _cookies.getCookies();
}
@@ -720,7 +721,7 @@ public class Request implements HttpServletRequest
//Javadoc for Request.getCookies() stipulates null for no cookies
if (_cookies == null || _cookies.getCookies().length == 0)
return null;
-
+
return _cookies.getCookies();
}
@@ -824,7 +825,7 @@ public class Request implements HttpServletRequest
{
if (_metadata==null)
return Locale.getDefault();
-
+
Enumeration enm = _metadata.getFields().getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
// handle no locale
@@ -864,7 +865,7 @@ public class Request implements HttpServletRequest
{
if (_metadata==null)
return Collections.enumeration(__defaultLocale);
-
+
Enumeration enm = _metadata.getFields().getValues(HttpHeader.ACCEPT_LANGUAGE.toString(),HttpFields.__separators);
// handle no locale
@@ -920,7 +921,7 @@ public class Request implements HttpServletRequest
LOG.ignore(e);
}
}
-
+
InetSocketAddress local=_channel.getLocalAddress();
if (local==null)
return "";
@@ -937,22 +938,25 @@ public class Request implements HttpServletRequest
@Override
public String getLocalName()
{
- if (_channel==null)
+ if (_channel!=null)
{
- try
- {
- String name =InetAddress.getLocalHost().getHostName();
- if (StringUtil.ALL_INTERFACES.equals(name))
- return null;
- return name;
- }
- catch (java.net.UnknownHostException e)
- {
- LOG.ignore(e);
- }
+ InetSocketAddress local=_channel.getLocalAddress();
+ if (local!=null)
+ return local.getHostString();
}
- InetSocketAddress local=_channel.getLocalAddress();
- return local.getHostString();
+
+ try
+ {
+ String name =InetAddress.getLocalHost().getHostName();
+ if (StringUtil.ALL_INTERFACES.equals(name))
+ return null;
+ return name;
+ }
+ catch (java.net.UnknownHostException e)
+ {
+ LOG.ignore(e);
+ }
+ return null;
}
/* ------------------------------------------------------------ */
@@ -965,7 +969,7 @@ public class Request implements HttpServletRequest
if (_channel==null)
return 0;
InetSocketAddress local=_channel.getLocalAddress();
- return local.getPort();
+ return local==null?0:local.getPort();
}
/* ------------------------------------------------------------ */
@@ -975,7 +979,7 @@ public class Request implements HttpServletRequest
@Override
public String getMethod()
{
- return _metadata.getMethod();
+ return _metadata==null?null:_metadata.getMethod();
}
/* ------------------------------------------------------------ */
@@ -1042,7 +1046,7 @@ public class Request implements HttpServletRequest
{
if (_queryParameters == null)
extractQueryParameters();
-
+
if (_queryParameters==NO_PARAMS || _queryParameters.size()==0)
_parameters=_contentParameters;
else if (_contentParameters==NO_PARAMS || _contentParameters.size()==0)
@@ -1190,7 +1194,7 @@ public class Request implements HttpServletRequest
/* ------------------------------------------------------------ */
/**
* Access the underlying Remote {@link InetSocketAddress} for this request.
- *
+ *
* @return the remote {@link InetSocketAddress} for this request, or null if the request has no remote (see {@link ServletRequest#getRemoteAddr()} for
* conditions that result in no remote address)
*/
@@ -1213,14 +1217,14 @@ public class Request implements HttpServletRequest
InetSocketAddress remote=_remote;
if (remote==null)
remote=_channel.getRemoteAddress();
-
+
if (remote==null)
return "";
-
+
InetAddress address = remote.getAddress();
if (address==null)
return remote.getHostString();
-
+
return address.getHostAddress();
}
@@ -1270,6 +1274,8 @@ public class Request implements HttpServletRequest
@Override
public RequestDispatcher getRequestDispatcher(String path)
{
+ path = URIUtil.compactPath(path);
+
if (path == null || _context == null)
return null;
@@ -1353,7 +1359,7 @@ public class Request implements HttpServletRequest
@Override
public String getScheme()
{
- String scheme=_metadata.getURI().getScheme();
+ String scheme=_metadata==null?null:_metadata.getURI().getScheme();
return scheme==null?HttpScheme.HTTP.asString():scheme;
}
@@ -1365,7 +1371,7 @@ public class Request implements HttpServletRequest
public String getServerName()
{
String name = _metadata.getURI().getHost();
-
+
// Return already determined host
if (name != null)
return name;
@@ -1414,7 +1420,7 @@ public class Request implements HttpServletRequest
{
HttpURI uri = _metadata.getURI();
int port = (uri.getHost()==null)?findServerPort():uri.getPort();
-
+
// If no port specified, return the default port for the scheme
if (port <= 0)
{
@@ -1422,7 +1428,7 @@ public class Request implements HttpServletRequest
return 443;
return 80;
}
-
+
// return a specific port
return port;
}
@@ -1445,10 +1451,10 @@ public class Request implements HttpServletRequest
// Return host from connection
if (_channel != null)
return getLocalPort();
-
+
return -1;
}
-
+
/* ------------------------------------------------------------ */
@Override
public ServletContext getServletContext()
@@ -1534,7 +1540,7 @@ public class Request implements HttpServletRequest
if (!create)
return null;
-
+
if (getResponse().isCommitted())
throw new IllegalStateException("Response is committed");
@@ -1578,6 +1584,14 @@ public class Request implements HttpServletRequest
return _metadata==null?null:_metadata.getURI();
}
+ /* ------------------------------------------------------------ */
+ /**
+ * @return Returns the original uri passed in metadata before customization/rewrite
+ */
+ public String getOriginalURI()
+ {
+ return _originalURI;
+ }
/* ------------------------------------------------------------ */
/**
* @param uri the URI to set
@@ -1631,7 +1645,7 @@ public class Request implements HttpServletRequest
UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
return user.getUserPrincipal();
}
-
+
return null;
}
@@ -1739,7 +1753,6 @@ public class Request implements HttpServletRequest
return _savedNewSessions.get(key);
}
-
/* ------------------------------------------------------------ */
/**
* @param request the Request metadata
@@ -1747,9 +1760,10 @@ public class Request implements HttpServletRequest
public void setMetaData(org.eclipse.jetty.http.MetaData.Request request)
{
_metadata=request;
+ _originalURI=_metadata.getURIString();
setMethod(request.getMethod());
HttpURI uri = request.getURI();
-
+
String path = uri.getDecodedPath();
String info;
if (path==null || path.length()==0)
@@ -1777,30 +1791,37 @@ public class Request implements HttpServletRequest
}
else
info = URIUtil.canonicalPath(path);// TODO should this be done prior to decoding???
-
+
if (info == null)
{
setPathInfo(path);
throw new BadMessageException(400,"Bad URI");
}
-
+
setPathInfo(info);
}
+ /* ------------------------------------------------------------ */
+ public org.eclipse.jetty.http.MetaData.Request getMetaData()
+ {
+ return _metadata;
+ }
+
/* ------------------------------------------------------------ */
public boolean hasMetaData()
{
return _metadata!=null;
}
-
+
/* ------------------------------------------------------------ */
protected void recycle()
{
_metadata=null;
-
+ _originalURI=null;
+
if (_context != null)
throw new IllegalStateException("Request in context!");
-
+
if (_inputState == __READER)
{
try
@@ -1913,7 +1934,7 @@ public class Request implements HttpServletRequest
setQueryEncoding(value == null?null:value.toString());
else if ("org.eclipse.jetty.server.sendContent".equals(name))
LOG.warn("Deprecated: org.eclipse.jetty.server.sendContent");
-
+
if (_attributes == null)
_attributes = new AttributesMap();
_attributes.setAttribute(name,value);
@@ -2092,7 +2113,7 @@ public class Request implements HttpServletRequest
*
* The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
*
- * @param queryEncoding the URI query character encoding
+ * @param queryEncoding the URI query character encoding
*/
public void setQueryEncoding(String queryEncoding)
{
@@ -2119,7 +2140,7 @@ public class Request implements HttpServletRequest
{
_remote = addr;
}
-
+
/* ------------------------------------------------------------ */
/**
* @param requestedSessionId
@@ -2165,7 +2186,7 @@ public class Request implements HttpServletRequest
*/
public void setAuthority(String host,int port)
{
- _metadata.getURI().setAuthority(host,port);;
+ _metadata.getURI().setAuthority(host,port);
}
/* ------------------------------------------------------------ */
@@ -2244,7 +2265,13 @@ public class Request implements HttpServletRequest
@Override
public String toString()
{
- return (_handled?"[":"(") + getMethod() + " " + _metadata.getURI() + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+ return String.format("%s%s%s %s%s@%x",
+ getClass().getSimpleName(),
+ _handled ? "[" : "(",
+ getMethod(),
+ getHttpURI(),
+ _handled ? "]" : ")",
+ hashCode());
}
/* ------------------------------------------------------------ */
@@ -2286,14 +2313,14 @@ public class Request implements HttpServletRequest
if (_multiPartInputStream == null)
{
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
-
+
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
-
+
_multiPartInputStream = new MultiPartInputStreamParser(getInputStream(),
- getContentType(), config,
+ getContentType(), config,
(_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
-
+
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
setAttribute(__MULTIPART_CONTEXT, _context);
Collection parts = _multiPartInputStream.getParts(); //causes parsing
@@ -2315,7 +2342,7 @@ public class Request implements HttpServletRequest
IO.copy(is, os);
String content=new String(os.toByteArray(),charset==null?StandardCharsets.UTF_8:Charset.forName(charset));
if (_contentParameters == null)
- _contentParameters = params == null ? new MultiMap() : params;
+ _contentParameters = params == null ? new MultiMap<>() : params;
_contentParameters.add(mp.getName(), content);
}
os.reset();
@@ -2355,7 +2382,7 @@ public class Request implements HttpServletRequest
public void mergeQueryParameters(String oldQuery,String newQuery, boolean updateQueryString)
{
// TODO This is seriously ugly
-
+
MultiMap newQueryParams = null;
// Have to assume ENCODING because we can't know otherwise.
if (newQuery!=null)
@@ -2388,24 +2415,32 @@ public class Request implements HttpServletRequest
if (updateQueryString)
{
- // Build the new merged query string, parameters in the
- // new query string hide parameters in the old query string.
- StringBuilder mergedQuery = new StringBuilder();
- if (newQuery!=null)
- mergedQuery.append(newQuery);
- for (Map.Entry> entry : mergedQueryParams.entrySet())
+ if (newQuery==null)
+ setQueryString(oldQuery);
+ else if (oldQuery==null)
+ setQueryString(newQuery);
+ else
{
- if (newQueryParams!=null && newQueryParams.containsKey(entry.getKey()))
- continue;
- for (String value : entry.getValue())
+ // Build the new merged query string, parameters in the
+ // new query string hide parameters in the old query string.
+ StringBuilder mergedQuery = new StringBuilder();
+ if (newQuery!=null)
+ mergedQuery.append(newQuery);
+ for (Map.Entry> entry : mergedQueryParams.entrySet())
{
- if (mergedQuery.length()>0)
- mergedQuery.append("&");
- mergedQuery.append(entry.getKey()).append("=").append(value);
+ if (newQueryParams!=null && newQueryParams.containsKey(entry.getKey()))
+ continue;
+ for (String value : entry.getValue())
+ {
+ if (mergedQuery.length()>0)
+ mergedQuery.append("&");
+ URIUtil.encodePath(mergedQuery,entry.getKey());
+ mergedQuery.append('=');
+ URIUtil.encodePath(mergedQuery,value);
+ }
}
+ setQueryString(mergedQuery.toString());
}
-
- setQueryString(mergedQuery.toString());
}
}
@@ -2415,25 +2450,6 @@ public class Request implements HttpServletRequest
@Override
public T upgrade(Class handlerClass) throws IOException, ServletException
{
- if (getContext() == null)
- throw new ServletException ("Unable to instantiate "+handlerClass);
-
- try
- {
- //Instantiate an instance and inject it
- T h = getContext().createInstance(handlerClass);
-
- //TODO handle the rest of the upgrade process
-
- return h;
- }
- catch (Exception e)
- {
- if (e instanceof ServletException)
- throw (ServletException)e;
- throw new ServletException(e);
- }
+ throw new ServletException("HttpServletRequest.upgrade() not supported in Jetty");
}
-
-
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java
index 2359c32616e..b6cfa1cea33 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java
@@ -18,10 +18,10 @@
package org.eclipse.jetty.server;
-import java.util.ArrayList;
-
import static java.util.Arrays.asList;
+import java.util.ArrayList;
+
class RequestLogCollection
implements RequestLog
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
index b9e73bdd3f5..a209bf109fb 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java
@@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
@@ -45,8 +46,8 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
-
-public class ResourceCache
+// TODO rename to ContentCache
+public class ResourceCache implements HttpContent.Factory
{
private static final Logger LOG = Log.getLogger(ResourceCache.class);
@@ -56,7 +57,8 @@ public class ResourceCache
private final ResourceFactory _factory;
private final ResourceCache _parent;
private final MimeTypes _mimeTypes;
- private final boolean _etagSupported;
+ private final boolean _etags;
+ private final boolean _gzip;
private final boolean _useFileMappedBuffer;
private int _maxCachedFileSize =128*1024*1024;
@@ -70,8 +72,9 @@ public class ResourceCache
* @param mimeTypes Mimetype to use for meta data
* @param useFileMappedBuffer true to file memory mapped buffers
* @param etags true to support etags
+ * @param gzip true to support gzip
*/
- public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
+ public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags,boolean gzip)
{
_factory = factory;
_cache=new ConcurrentHashMap();
@@ -80,7 +83,8 @@ public class ResourceCache
_mimeTypes=mimeTypes;
_parent=parent;
_useFileMappedBuffer=useFileMappedBuffer;
- _etagSupported=etags;
+ _etags=etags;
+ _gzip=gzip;
}
/* ------------------------------------------------------------ */
@@ -163,6 +167,14 @@ public class ResourceCache
}
}
+ /* ------------------------------------------------------------ */
+ @Deprecated
+ public HttpContent lookup(String pathInContext)
+ throws IOException
+ {
+ return getContent(pathInContext);
+ }
+
/* ------------------------------------------------------------ */
/** Get a Entry from the cache.
* Get either a valid entry object or create a new one if possible.
@@ -174,7 +186,8 @@ public class ResourceCache
* the resource does not exist, then null is returned.
* @throws IOException Problem loading the resource
*/
- public HttpContent lookup(String pathInContext)
+ @Override
+ public HttpContent getContent(String pathInContext)
throws IOException
{
// Is the content in this cache?
@@ -206,6 +219,9 @@ public class ResourceCache
*/
protected boolean isCacheable(Resource resource)
{
+ if (_maxCachedFiles<=0)
+ return false;
+
long len = resource.length();
// Will it fit in the cache?
@@ -216,16 +232,43 @@ public class ResourceCache
private HttpContent load(String pathInContext, Resource resource)
throws IOException
{
- CachedHttpContent content=null;
if (resource==null || !resource.exists())
return null;
+ if (resource.isDirectory())
+ return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize());
+
// Will it fit in the cache?
- if (!resource.isDirectory() && isCacheable(resource))
+ if (isCacheable(resource))
{
- // Create the Content (to increment the cache sizes before adding the content
- content = new CachedHttpContent(pathInContext,resource);
+ CachedHttpContent content=null;
+
+ // Look for a gzip resource
+ if (_gzip)
+ {
+ String pathInContextGz=pathInContext+".gz";
+ CachedHttpContent contentGz = _cache.get(pathInContextGz);
+ if (contentGz==null || !contentGz.isValid())
+ {
+ contentGz=null;
+ Resource resourceGz=_factory.getResource(pathInContextGz);
+ if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length()=resource.lastModified())
+ return new ResourceHttpContent(resource,mt,getMaxCachedFileSize(),contentGz);
+
+ // Is there a gzip resource?
+ Resource resourceGz=_factory.getResource(pathInContextGz);
+ if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length() _indirectBuffer=new AtomicReference();
AtomicReference _directBuffer=new AtomicReference();
/* ------------------------------------------------------------ */
- CachedHttpContent(String pathInContext,Resource resource)
+ CachedHttpContent(String pathInContext,Resource resource,CachedHttpContent gzipped)
{
_key=pathInContext;
_resource=resource;
@@ -365,9 +425,11 @@ public class ResourceCache
_cachedFiles.incrementAndGet();
_lastAccessed=System.currentTimeMillis();
- _etag=ResourceCache.this._etagSupported?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
+ _etag=ResourceCache.this._etags?new PreEncodedHttpField(HttpHeader.ETAG,resource.getWeakETag()):null;
+
+ _gzipped=gzipped==null?null:new CachedGzipHttpContent(this,gzipped);
}
-
+
/* ------------------------------------------------------------ */
public String getKey()
@@ -428,7 +490,7 @@ public class ResourceCache
// Invalidate it
_cachedSize.addAndGet(-_contentLengthValue);
_cachedFiles.decrementAndGet();
- _resource.close();
+ _resource.close();
}
/* ------------------------------------------------------------ */
@@ -459,6 +521,20 @@ public class ResourceCache
{
return _contentType==null?null:_contentType.getValue();
}
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpField getContentEncoding()
+ {
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public String getContentEncodingValue()
+ {
+ return null;
+ }
/* ------------------------------------------------------------ */
@Override
@@ -479,7 +555,6 @@ public class ResourceCache
@Override
public void release()
{
- // don't release while cached. Release when invalidated.
}
/* ------------------------------------------------------------ */
@@ -557,12 +632,65 @@ public class ResourceCache
return _resource.getReadableByteChannel();
}
-
/* ------------------------------------------------------------ */
@Override
public String toString()
{
- return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType);
- }
+ return String.format("CachedContent@%x{r=%s,e=%b,lm=%s,ct=%s,gz=%b}",hashCode(),_resource,_resource.exists(),_lastModified,_contentType,_gzipped!=null);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public HttpContent getGzipContent()
+ {
+ return (_gzipped!=null && _gzipped.isValid())?_gzipped:null;
+ }
}
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ public class CachedGzipHttpContent extends GzipHttpContent
+ {
+ private final CachedHttpContent _content;
+ private final CachedHttpContent _contentGz;
+ private final HttpField _etag;
+
+ CachedGzipHttpContent(CachedHttpContent content, CachedHttpContent contentGz)
+ {
+ super(content,contentGz);
+ _content=content;
+ _contentGz=contentGz;
+
+ _etag=(ResourceCache.this._etags)?new PreEncodedHttpField(HttpHeader.ETAG,_content.getResource().getWeakETag("--gzip")):null;
+ }
+
+ public boolean isValid()
+ {
+ return _contentGz.isValid() && _content.isValid() && _content.getResource().lastModified() <= _contentGz.getResource().lastModified();
+ }
+
+ @Override
+ public HttpField getETag()
+ {
+ if (_etag!=null)
+ return _etag;
+ return super.getETag();
+ }
+
+ @Override
+ public String getETagValue()
+ {
+ if (_etag!=null)
+ return _etag.getValue();
+ return super.getETagValue();
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Cached"+super.toString();
+ }
+ }
+
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
new file mode 100644
index 00000000000..2e0edde6730
--- /dev/null
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java
@@ -0,0 +1,104 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpContent.Factory;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.ResourceHttpContent;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+public class ResourceContentFactory implements Factory
+{
+ private final ResourceFactory _factory;
+ private final MimeTypes _mimeTypes;
+ private final int _maxBufferSize;
+ private final boolean _gzip;
+
+
+ /* ------------------------------------------------------------ */
+ public ResourceContentFactory(ResourceFactory factory, MimeTypes mimeTypes, int maxBufferSize, boolean gzip)
+ {
+ _factory=factory;
+ _mimeTypes=mimeTypes;
+ _maxBufferSize=maxBufferSize;
+ _gzip=gzip;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Get a Entry from the cache.
+ * Get either a valid entry object or create a new one if possible.
+ *
+ * @param pathInContext The key into the cache
+ * @return The entry matching pathInContext, or a new entry
+ * if no matching entry was found. If the content exists but is not cachable,
+ * then a {@link ResourceHttpContent} instance is return. If
+ * the resource does not exist, then null is returned.
+ * @throws IOException Problem loading the resource
+ */
+ @Override
+ public HttpContent getContent(String pathInContext)
+ throws IOException
+ {
+
+ // try loading the content from our factory.
+ Resource resource=_factory.getResource(pathInContext);
+ HttpContent loaded = load(pathInContext,resource);
+ return loaded;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ private HttpContent load(String pathInContext, Resource resource)
+ throws IOException
+ {
+ if (resource==null || !resource.exists())
+ return null;
+
+ if (resource.isDirectory())
+ return new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_maxBufferSize);
+
+ // Look for a gzip resource or content
+ String mt = _mimeTypes.getMimeByExtension(pathInContext);
+ if (_gzip)
+ {
+ // Is there a gzip resource?
+ String pathInContextGz=pathInContext+".gz";
+ Resource resourceGz=_factory.getResource(pathInContextGz);
+ if (resourceGz.exists() && resourceGz.lastModified()>=resource.lastModified() && resourceGz.length() __cookieBuilder = new ThreadLocal()
@@ -81,7 +80,7 @@ public class Response implements HttpServletResponse
return new StringBuilder(128);
}
};
-
+
public enum OutputType
{
NONE, STREAM, WRITER
@@ -114,7 +113,7 @@ public class Response implements HttpServletResponse
private OutputType _outputType = OutputType.NONE;
private ResponseWriter _writer;
private long _contentLength = -1;
-
+
public Response(HttpChannel channel, HttpOutput out)
{
@@ -141,7 +140,7 @@ public class Response implements HttpServletResponse
_fields.clear();
_explicitEncoding=false;
}
-
+
public HttpOutput getHttpOutput()
{
return _out;
@@ -178,7 +177,7 @@ public class Response implements HttpServletResponse
cookie.getComment(),
cookie.isSecure(),
cookie.isHttpOnly(),
- cookie.getVersion());;
+ cookie.getVersion());
}
@Override
@@ -241,13 +240,13 @@ public class Response implements HttpServletResponse
// Format value and params
StringBuilder buf = __cookieBuilder.get();
buf.setLength(0);
-
+
// Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting
boolean quote_name=isQuoteNeededForCookie(name);
quoteOnlyOrAppend(buf,name,quote_name);
-
+
buf.append('=');
-
+
// Remember name= part to look for other matching set-cookie
String name_equals=buf.toString();
@@ -260,7 +259,7 @@ public class Response implements HttpServletResponse
boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
boolean has_path = path!=null && path.length()>0;
boolean quote_path = has_path && isQuoteNeededForCookie(path);
-
+
// Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path ||
QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) ||
@@ -272,14 +271,14 @@ public class Response implements HttpServletResponse
buf.append (";Version=1");
else if (version>1)
buf.append (";Version=").append(version);
-
+
// Append path
if (has_path)
{
buf.append(";Path=");
quoteOnlyOrAppend(buf,path,quote_path);
}
-
+
// Append domain
if (has_domain)
{
@@ -297,7 +296,7 @@ public class Response implements HttpServletResponse
buf.append(__01Jan1970_COOKIE);
else
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
-
+
// for v1 cookies, also send max-age
if (version>=1)
{
@@ -336,7 +335,7 @@ public class Response implements HttpServletResponse
}
}
}
-
+
// add the set cookie
_fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString());
@@ -355,7 +354,7 @@ public class Response implements HttpServletResponse
{
if (s==null || s.length()==0)
return true;
-
+
if (QuotedStringTokenizer.isQuoted(s))
return false;
@@ -364,15 +363,15 @@ public class Response implements HttpServletResponse
char c = s.charAt(i);
if (__COOKIE_DELIM.indexOf(c)>=0)
return true;
-
+
if (c<0x20 || c>=0x7f)
throw new IllegalArgumentException("Illegal character in cookie value");
}
return false;
}
-
-
+
+
private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
{
if (quote)
@@ -380,7 +379,7 @@ public class Response implements HttpServletResponse
else
buf.append(s);
}
-
+
@Override
public boolean containsHeader(String name)
{
@@ -404,7 +403,7 @@ public class Response implements HttpServletResponse
int port = uri.getPort();
if (port < 0)
port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
-
+
// Is it the same server?
if (!request.getServerName().equalsIgnoreCase(uri.getHost()))
return url;
@@ -422,7 +421,7 @@ public class Response implements HttpServletResponse
return null;
// should not encode if cookies in evidence
- if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
+ if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs())
{
int prefix = url.indexOf(sessionURLPrefix);
if (prefix != -1)
@@ -524,7 +523,7 @@ public class Response implements HttpServletResponse
LOG.debug("Aborting on sendError on committed response {} {}",code,message);
code=-1;
}
-
+
switch(code)
{
case -1:
@@ -534,91 +533,44 @@ public class Response implements HttpServletResponse
sendProcessing();
return;
default:
+ break;
}
- if (isCommitted())
- LOG.warn("Committed before "+code+" "+message);
-
resetBuffer();
+ _mimeType=null;
_characterEncoding=null;
+ _outputType = OutputType.NONE;
setHeader(HttpHeader.EXPIRES,null);
setHeader(HttpHeader.LAST_MODIFIED,null);
setHeader(HttpHeader.CACHE_CONTROL,null);
setHeader(HttpHeader.CONTENT_TYPE,null);
- setHeader(HttpHeader.CONTENT_LENGTH,null);
+ setHeader(HttpHeader.CONTENT_LENGTH, null);
- _outputType = OutputType.NONE;
setStatus(code);
- _reason=message;
Request request = _channel.getRequest();
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
if (message==null)
- message=cause==null?HttpStatus.getMessage(code):cause.toString();
+ {
+ _reason=HttpStatus.getMessage(code);
+ message=cause==null?_reason:cause.toString();
+ }
+ else
+ _reason=message;
- // If we are allowed to have a body
- if (code!=SC_NO_CONTENT &&
- code!=SC_NOT_MODIFIED &&
- code!=SC_PARTIAL_CONTENT &&
- code>=SC_OK)
+ // If we are allowed to have a body, then produce the error page.
+ if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED &&
+ code != SC_PARTIAL_CONTENT && code >= SC_OK)
{
- ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler());
- if (error_handler!=null)
- {
- request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
- request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
- request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
- request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
- error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this );
- }
- else
- {
- setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
- setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString());
- try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);)
- {
- message=StringUtil.sanitizeXmlString(message);
- String uri= request.getRequestURI();
- uri=StringUtil.sanitizeXmlString(uri);
-
- writer.write("\n\n\n");
- writer.write("Error ");
- writer.write(Integer.toString(code));
- writer.write(' ');
- if (message==null)
- writer.write(message);
- writer.write("\n\n\n
Problem accessing ");
- writer.write(uri);
- writer.write(". Reason:\n
");
- writer.write(message);
- writer.write("
");
- writer.write("
\n");
-
- getHttpChannel().getHttpConfiguration().writePoweredBy(writer,null,"");
- writer.write("\n\n\n");
-
- writer.flush();
- setContentLength(writer.size());
- try (ServletOutputStream outputStream = getOutputStream())
- {
- writer.writeTo(outputStream);
- writer.destroy();
- }
- }
- }
+ ContextHandler.Context context = request.getContext();
+ ContextHandler contextHandler = context == null ? _channel.getState().getContextHandler() : context.getContextHandler();
+ ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(), contextHandler);
+ request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, code);
+ request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+ request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+ request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, request.getServletName());
+ error_handler.handle(null, request, request, this);
}
- else if (code!=SC_PARTIAL_CONTENT)
- {
- // TODO work out why this is required?
- _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE);
- _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH);
- _characterEncoding=null;
- _mimeType=null;
- }
-
- closeOutput();
}
/**
@@ -637,7 +589,7 @@ public class Response implements HttpServletResponse
_channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true);
}
}
-
+
/**
* Sends a response with one of the 300 series redirection codes.
* @param code the redirect status code
@@ -648,7 +600,7 @@ public class Response implements HttpServletResponse
{
if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST))
throw new IllegalArgumentException("Not a 3xx redirect code");
-
+
if (isIncluding())
return;
@@ -672,11 +624,11 @@ public class Response implements HttpServletResponse
if (!location.startsWith("/"))
buf.append('/');
}
-
+
if(location==null)
throw new IllegalStateException("path cannot be above root");
buf.append(location);
-
+
location=buf.toString();
}
@@ -791,13 +743,13 @@ public class Response implements HttpServletResponse
setContentType(value);
return;
}
-
+
if (HttpHeader.CONTENT_LENGTH.is(name))
{
setHeader(name,value);
return;
}
-
+
_fields.add(name, value);
}
@@ -822,7 +774,7 @@ public class Response implements HttpServletResponse
_contentLength = value;
}
}
-
+
@Override
public void setStatus(int sc)
{
@@ -841,7 +793,7 @@ public class Response implements HttpServletResponse
{
setStatusWithReason(sc,sm);
}
-
+
public void setStatusWithReason(int sc, String sm)
{
if (sc <= 0)
@@ -903,9 +855,9 @@ public class Response implements HttpServletResponse
setCharacterEncoding(encoding,false);
}
}
-
+
Locale locale = getLocale();
-
+
if (_writer != null && _writer.isFor(locale,encoding))
_writer.reopen();
else
@@ -917,7 +869,7 @@ public class Response implements HttpServletResponse
else
_writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),locale,encoding);
}
-
+
// Set the output type at the end, because setCharacterEncoding() checks for it
_outputType = OutputType.WRITER;
}
@@ -939,7 +891,7 @@ public class Response implements HttpServletResponse
long written = _out.getWritten();
if (written > len)
throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written);
-
+
_fields.putLongField(HttpHeader.CONTENT_LENGTH, len);
if (isAllContentWritten(written))
{
@@ -963,7 +915,7 @@ public class Response implements HttpServletResponse
else
_fields.remove(HttpHeader.CONTENT_LENGTH);
}
-
+
public long getContentLength()
{
return _contentLength;
@@ -1006,7 +958,7 @@ public class Response implements HttpServletResponse
_contentLength = len;
_fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len);
}
-
+
@Override
public void setContentLengthLong(long length)
{
@@ -1018,7 +970,7 @@ public class Response implements HttpServletResponse
{
setCharacterEncoding(encoding,true);
}
-
+
private void setCharacterEncoding(String encoding, boolean explicit)
{
if (isIncluding() || isWriting())
@@ -1029,12 +981,12 @@ public class Response implements HttpServletResponse
if (encoding == null)
{
_explicitEncoding=false;
-
+
// Clear any encoding.
if (_characterEncoding != null)
{
_characterEncoding = null;
-
+
if (_mimeType!=null)
{
_mimeType=_mimeType.getBaseType();
@@ -1070,7 +1022,7 @@ public class Response implements HttpServletResponse
}
}
}
-
+
@Override
public void setContentType(String contentType)
{
@@ -1092,7 +1044,7 @@ public class Response implements HttpServletResponse
{
_contentType = contentType;
_mimeType = MimeTypes.CACHE.get(contentType);
-
+
String charset;
if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
charset=_mimeType.getCharsetString();
@@ -1129,7 +1081,7 @@ public class Response implements HttpServletResponse
_fields.put(_mimeType.getContentTypeField());
}
}
-
+
}
@Override
@@ -1165,7 +1117,7 @@ public class Response implements HttpServletResponse
_fields.clear();
String connection = _channel.getRequest().getHeader(HttpHeader.CONNECTION.asString());
-
+
if (connection != null)
{
for (String value: StringUtil.csvSplit(null,connection,0,connection.length()))
@@ -1195,12 +1147,12 @@ public class Response implements HttpServletResponse
}
public void reset(boolean preserveCookies)
- {
+ {
if (!preserveCookies)
reset();
else
{
- ArrayList cookieValues = new ArrayList(5);
+ ArrayList cookieValues = new ArrayList<>(5);
Enumeration vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
while (vals.hasMoreElements())
cookieValues.add(vals.nextElement());
@@ -1229,11 +1181,11 @@ public class Response implements HttpServletResponse
{
return new MetaData.Response(_channel.getRequest().getHttpVersion(), getStatus(), getReason(), _fields, getLongContentLength());
}
-
+
/** Get the MetaData.Response committed for this response.
- * This may differ from the meta data in this response for
+ * This may differ from the meta data in this response for
* exceptional responses (eg 4xx and 5xx responses generated
- * by the container) and the committedMetaData should be used
+ * by the container) and the committedMetaData should be used
* for logging purposes.
* @return The committed MetaData or a {@link #newResponseMetaData()}
* if not yet committed.
@@ -1307,11 +1259,10 @@ public class Response implements HttpServletResponse
{
return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields);
}
-
+
public void putHeaders(HttpContent content,long contentLength, boolean etag)
{
-
HttpField lm = content.getLastModified();
if (lm!=null)
_fields.put(lm);
@@ -1335,7 +1286,11 @@ public class Response implements HttpServletResponse
_characterEncoding=content.getCharacterEncoding();
_mimeType=content.getMimeType();
}
-
+
+ HttpField ce=content.getContentEncoding();
+ if (ce!=null)
+ _fields.put(ce);
+
if (etag)
{
HttpField et = content.getETag();
@@ -1343,9 +1298,9 @@ public class Response implements HttpServletResponse
_fields.put(et);
}
}
-
+
public static void putHeaders(HttpServletResponse response, HttpContent content, long contentLength, boolean etag)
- {
+ {
long lml=content.getResource().lastModified();
if (lml>=0)
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml);
@@ -1362,8 +1317,12 @@ public class Response implements HttpServletResponse
String ct=content.getContentTypeValue();
if (ct!=null && response.getContentType()==null)
- response.setContentType(content.getContentTypeValue());
-
+ response.setContentType(ct);
+
+ String ce=content.getContentEncodingValue();
+ if (ce!=null)
+ response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),ce);
+
if (etag)
{
String et=content.getETagValue();
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
index 77d4d9206c1..aefb547f2a6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java
@@ -27,6 +27,7 @@ import javax.servlet.ServletRequest;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
import org.eclipse.jetty.util.TypeUtil;
@@ -66,15 +67,26 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
@Override
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
{
- if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint)
+ EndPoint endp = request.getHttpChannel().getEndPoint();
+ if (endp instanceof DecryptedEndPoint)
{
- request.setScheme(HttpScheme.HTTPS.asString());
- request.setSecure(true);
- SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint();
+ SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)endp;
SslConnection sslConnection = ssl_endp.getSslConnection();
SSLEngine sslEngine=sslConnection.getSSLEngine();
customize(sslEngine,request);
+
+ if (request.getHttpURI().getScheme()==null)
+ request.setScheme(HttpScheme.HTTPS.asString());
}
+ else if (endp instanceof ProxyConnectionFactory.ProxyEndPoint)
+ {
+ ProxyConnectionFactory.ProxyEndPoint proxy = (ProxyConnectionFactory.ProxyEndPoint)endp;
+ if (request.getHttpURI().getScheme()==null && proxy.getAttribute(ProxyConnectionFactory.TLS_VERSION)!=null)
+ request.setScheme(HttpScheme.HTTPS.asString());
+ }
+
+ if (HttpScheme.HTTPS.is(request.getScheme()))
+ request.setSecure(true);
}
/**
@@ -101,7 +113,6 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
*/
public void customize(SSLEngine sslEngine, Request request)
{
- request.setScheme(HttpScheme.HTTPS.asString());
SSLSession sslSession = sslEngine.getSession();
if (_sniHostCheck)
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
index 1665a19b16c..4cd668dd958 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -24,6 +24,7 @@ import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.Channel;
+import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
@@ -32,6 +33,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@@ -229,7 +231,6 @@ public class ServerConnector extends AbstractNetworkConnector
_manager = newSelectorManager(getExecutor(), getScheduler(),
selectors>0?selectors:Math.max(1,Math.min(4,Runtime.getRuntime().availableProcessors()/2)));
addBean(_manager, true);
- setSelectorPriorityDelta(-1);
setAcceptorPriorityDelta(-2);
}
@@ -426,7 +427,7 @@ public class ServerConnector extends AbstractNetworkConnector
return _localPort;
}
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout());
}
@@ -493,19 +494,19 @@ public class ServerConnector extends AbstractNetworkConnector
}
@Override
- protected void accepted(SocketChannel channel) throws IOException
+ protected void accepted(SelectableChannel channel) throws IOException
{
- ServerConnector.this.accepted(channel);
+ ServerConnector.this.accepted((SocketChannel)channel);
}
@Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
+ protected ChannelEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{
- return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey);
+ return ServerConnector.this.newEndPoint((SocketChannel)channel, selectSet, selectionKey);
}
@Override
- public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
+ public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
index 198e5c4c25a..20fc7db1ea4 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -345,14 +345,12 @@ public class ShutdownMonitor
*/
private ShutdownMonitor()
{
- Properties props = System.getProperties();
-
- this.DEBUG = props.containsKey("DEBUG");
+ this.DEBUG = System.getProperty("DEBUG") != null;
// Use values passed thru via /jetty-start/
- this.host = props.getProperty("STOP.HOST","127.0.0.1");
- this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
- this.key = props.getProperty("STOP.KEY",null);
+ this.host = System.getProperty("STOP.HOST","127.0.0.1");
+ this.port = Integer.parseInt(System.getProperty("STOP.PORT","-1"));
+ this.key = System.getProperty("STOP.KEY",null);
this.exitVm = true;
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
index cdc402120c7..9ac93c7a525 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java
@@ -20,12 +20,12 @@ package org.eclipse.jetty.server;
import java.net.Socket;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.Connection.Listener;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
-import org.eclipse.jetty.io.EndPoint;
/* ------------------------------------------------------------ */
@@ -70,9 +70,9 @@ public class SocketCustomizationListener implements Listener
ssl=true;
}
- if (endp instanceof ChannelEndPoint)
+ if (endp instanceof SocketChannelEndPoint)
{
- Socket socket = ((ChannelEndPoint)endp).getSocket();
+ Socket socket = ((SocketChannelEndPoint)endp).getSocket();
customize(socket,connection.getClass(),ssl);
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
index d5fca99b093..7501d84defc 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java
@@ -21,7 +21,14 @@ package org.eclipse.jetty.server.handler;
import java.io.IOException;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
@@ -47,6 +54,31 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand
{
}
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ if (baseRequest.getDispatcherType()==DispatcherType.ERROR)
+ doError(target,baseRequest,request,response);
+ else
+ doHandle(target,baseRequest,request,response);
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ Object o = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+ int code = (o instanceof Integer)?((Integer)o).intValue():(o!=null?Integer.valueOf(o.toString()):500);
+ o = request.getAttribute(RequestDispatcher.ERROR_MESSAGE);
+ String reason = o!=null?o.toString():null;
+
+ response.sendError(code,reason);
+ }
+
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.LifeCycle#start()
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
index f6dad190583..8f9d14c1434 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AllowSymLinkAliasChecker.java
@@ -69,25 +69,42 @@ public class AllowSymLinkAliasChecker implements AliasCheck
return true;
}
}
-
+
// No, so let's check each element ourselves
- Path d = path.getRoot();
- for (Path e:path)
+ boolean linked=true;
+ Path target=path;
+ int loops=0;
+ while (linked)
{
- d=d.resolve(e);
-
- while (Files.exists(d) && Files.isSymbolicLink(d))
+ if (++loops>100)
{
- Path link=Files.readSymbolicLink(d);
- if (!link.isAbsolute())
- link=d.resolve(link);
- d=link;
+ if (LOG.isDebugEnabled())
+ LOG.debug("Too many symlinks {} --> {}",resource,target);
+ return false;
}
+ linked=false;
+ Path d = target.getRoot();
+ for (Path e:target)
+ {
+ Path r=d.resolve(e);
+ d=r;
+
+ while (Files.exists(d) && Files.isSymbolicLink(d))
+ {
+ Path link=Files.readSymbolicLink(d);
+ if (!link.isAbsolute())
+ link=d.getParent().resolve(link);
+ d=link;
+ linked=true;
+ }
+ }
+ target=d;
}
- if (pathResource.getAliasPath().equals(d))
+
+ if (pathResource.getAliasPath().equals(target))
{
if (LOG.isDebugEnabled())
- LOG.debug("Allow path symlink {} --> {}",resource,d);
+ LOG.debug("Allow path symlink {} --> {}",resource,target);
return true;
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index cbdde7fae5c..668df07f961 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -102,7 +102,7 @@ import org.eclipse.jetty.util.resource.Resource;
*
* This servers executore is made available via a context attributed "org.eclipse.jetty.server.Executor".
*
- * By default, the context is created with alias checkers for {@link AllowSymLinkAliasChecker} (unix only) and {@link ApproveNonExistentDirectoryAliases}.
+ * By default, the context is created with alias checkers for {@link AllowSymLinkAliasChecker} (unix only) and {@link ApproveNonExistentDirectoryAliases}.
* If these alias checkers are not required, then {@link #clearAliasChecks()} or {@link #setAliasChecks(List)} should be called.
*/
@ManagedObject("URI Context")
@@ -126,7 +126,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
private static final ThreadLocal __context = new ThreadLocal();
private static String __serverInfo = "jetty/" + Server.getVersion();
-
+
/**
* If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
* with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
@@ -603,7 +603,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (listener instanceof ContextScopeListener)
_contextListeners.add((ContextScopeListener)listener);
-
+
if (listener instanceof ServletContextListener)
_servletContextListeners.add((ServletContextListener)listener);
@@ -809,7 +809,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
-
+
/* ------------------------------------------------------------ */
protected void stopContext () throws Exception
{
@@ -824,9 +824,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
callContextDestroyed(_servletContextListeners.get(i),event);
}
}
-
-
-
+
+
+
/* ------------------------------------------------------------ */
protected void callContextInitialized (ServletContextListener l, ServletContextEvent e)
{
@@ -853,6 +853,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
_availability = Availability.UNAVAILABLE;
ClassLoader old_classloader = null;
+ ClassLoader old_webapploader = null;
Thread current_thread = null;
Context old_context = __context.get();
@@ -862,6 +863,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
// Set the classloader
if (_classLoader != null)
{
+ old_webapploader = _classLoader;
current_thread = Thread.currentThread();
old_classloader = current_thread.getContextClassLoader();
current_thread.setContextClassLoader(_classLoader);
@@ -885,14 +887,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
LOG.info("Stopped {}", this);
__context.set(old_context);
// reset the classloader
- if (_classLoader != null && current_thread!=null)
+ if ((old_classloader == null || (old_classloader != old_webapploader)) && current_thread != null)
current_thread.setContextClassLoader(old_classloader);
}
_scontext.clearAttributes();
}
-
-
+
+
/* ------------------------------------------------------------ */
public boolean checkVirtualHost(final Request baseRequest)
{
@@ -933,8 +935,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
return true;
}
-
-
+
+
/* ------------------------------------------------------------ */
public boolean checkContextPath(String uri)
{
@@ -1076,8 +1078,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
if (old_context != _scontext)
- enterScope(baseRequest);
-
+ enterScope(baseRequest,dispatch);
+
if (LOG.isDebugEnabled())
LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
@@ -1097,7 +1099,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (old_context != _scontext)
{
exitScope(baseRequest);
-
+
// reset the classloader
if (_classLoader != null && current_thread!=null)
{
@@ -1141,11 +1143,30 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
- if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+ switch(dispatch)
{
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
- baseRequest.setHandled(true);
- return;
+ case REQUEST:
+ if (isProtectedTarget(target))
+ {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ baseRequest.setHandled(true);
+ return;
+ }
+ break;
+
+ case ERROR:
+ // If this is already a dispatch to an error page, proceed normally
+ if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH)))
+ break;
+
+ Object error = request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
+ // We can just call sendError here. If there is no error page, then one will
+ // be generated. If there is an error page, then a RequestDispatcher will be
+ // used to route the request through appropriate filters etc.
+ response.sendError((error instanceof Integer)?((Integer)error).intValue():500);
+ return;
+ default:
+ break;
}
// start manual inline of nextHandle(target,baseRequest,request,response);
@@ -1179,13 +1200,14 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
-
-
+
+
/**
* @param request A request that is applicable to the scope, or null
+ * @param reason An object that indicates the reason the scope is being entered.
*/
- protected void enterScope(Request request)
+ protected void enterScope(Request request, Object reason)
{
if (!_contextListeners.isEmpty())
{
@@ -1193,7 +1215,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
try
{
- listener.enterScope(_scontext,request);
+ listener.enterScope(_scontext,request,reason);
}
catch(Throwable e)
{
@@ -1202,8 +1224,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
}
-
-
+
+
/**
* @param request A request that is applicable to the scope, or null
*/
@@ -1224,7 +1246,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
}
}
-
+
/* ------------------------------------------------------------ */
/**
* Handle a runnable in the scope of this context and a particular request
@@ -1235,10 +1257,18 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
{
ClassLoader old_classloader = null;
Thread current_thread = null;
- Context old_context = null;
+ Context old_context = __context.get();
+
+ // Are we already in the scope?
+ if (old_context==_scontext)
+ {
+ runnable.run();
+ return;
+ }
+
+ // Nope, so enter the scope and then exit
try
{
- old_context = __context.get();
__context.set(_scontext);
// Set the classloader
@@ -1249,21 +1279,21 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
current_thread.setContextClassLoader(_classLoader);
}
- enterScope(request);
+ enterScope(request,runnable);
runnable.run();
}
finally
{
exitScope(request);
-
+
__context.set(old_context);
- if (old_classloader != null && current_thread!=null)
+ if (old_classloader != null)
{
current_thread.setContextClassLoader(old_classloader);
}
}
}
-
+
/* ------------------------------------------------------------ */
/*
* Handle a runnable in the scope of this context
@@ -1473,10 +1503,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Set the base resource for this context.
- * @param resourceBase A string representing the base resource for the context. Any string accepted
- * by {@link Resource#newResource(String)} may be passed and the call is equivalent to
+ * @param resourceBase A string representing the base resource for the context. Any string accepted
+ * by {@link Resource#newResource(String)} may be passed and the call is equivalent to
* setBaseResource(newResource(resourceBase));
*/
public void setResourceBase(String resourceBase)
@@ -1643,7 +1673,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
return null;
if (_classLoader == null)
- return Loader.loadClass(this.getClass(),className);
+ return Loader.loadClass(className);
return _classLoader.loadClass(className);
}
@@ -1685,9 +1715,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Get all of the locale encodings
- *
+ *
* @return a map of all the locale encodings: key is name of the locale and value is the char encoding
*/
public Map getLocaleEncodings()
@@ -2287,7 +2317,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
try
{
@SuppressWarnings({ "unchecked", "rawtypes" })
- Class extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):(Class)_classLoader.loadClass(className);
+ Class extends EventListener> clazz = _classLoader==null?Loader.loadClass(className):(Class)_classLoader.loadClass(className);
addListener(clazz);
}
catch (ClassNotFoundException e)
@@ -2380,7 +2410,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
//classloader, or a parent of it
try
{
- Class> reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection");
+ Class> reflect = Loader.loadClass("sun.reflect.Reflection");
Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE);
Class> caller = (Class>)getCallerClass.invoke(null, 2);
@@ -2388,16 +2418,16 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
ClassLoader callerLoader = caller.getClassLoader();
while (!ok && callerLoader != null)
{
- if (callerLoader == _classLoader)
+ if (callerLoader == _classLoader)
ok = true;
else
- callerLoader = callerLoader.getParent();
+ callerLoader = callerLoader.getParent();
}
if (ok)
return _classLoader;
}
- catch (Exception e)
+ catch (Exception e)
{
LOG.warn("Unable to check classloader of caller",e);
}
@@ -2846,11 +2876,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
if (a.length()Component that handles Error Pages.
+ *
An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.
+ *
It is called by {@link HttpServletResponse#sendError(int)} to write an error page via
+ * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a
+ * dispatch cannot be done.
Generate a error response body to be sent for a bad message.
- * In this case there is something wrong with the request, so either
+ /**
+ *
Generate a error response body to be sent for a bad message.
+ *
In this case there is something wrong with the request, so either
* a request cannot be built, or it is not safe to build a request.
- * This method allows for a simple error page body to be returned
- * and some response headers to be set.
+ * This method allows for a simple error page body to be returned
+ * and some response headers to be set.
+ *
* @param status The error code that will be sent
* @param reason The reason for the error code (may be null)
* @param fields The header fields that will be sent with the response.
@@ -214,14 +224,14 @@ public class ErrorHandler extends AbstractHandler
*/
public ByteBuffer badMessageError(int status, String reason, HttpFields fields)
{
- if (reason==null)
- reason=HttpStatus.getMessage(status);
- fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString());
+ if (reason == null)
+ reason = HttpStatus.getMessage(status);
+ fields.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_HTML_8859_1.asString());
return BufferUtil.toBuffer("
Bad Message " + status + "
reason: " + reason + "
");
- }
-
+ }
+
/* ------------------------------------------------------------ */
- /** Get the cacheControl.
+ /**
* @return the cacheControl header to set on error responses.
*/
public String getCacheControl()
@@ -230,7 +240,7 @@ public class ErrorHandler extends AbstractHandler
}
/* ------------------------------------------------------------ */
- /** Set the cacheControl.
+ /**
* @param cacheControl the cacheControl header to set on error responses.
*/
public void setCacheControl(String cacheControl)
@@ -240,7 +250,7 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
/**
- * @return True if stack traces are shown in the error pages
+ * @return whether stack traces are shown in the error pages
*/
public boolean isShowStacks()
{
@@ -249,7 +259,7 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
/**
- * @param showStacks True if stack traces are shown in the error pages
+ * @param showStacks whether stack traces are shown in the error pages
*/
public void setShowStacks(boolean showStacks)
{
@@ -258,25 +268,27 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
/**
- * @param showMessageInTitle if true, the error message appears in page title
+ * @return whether the error message appears in page title
*/
- public void setShowMessageInTitle(boolean showMessageInTitle)
- {
- _showMessageInTitle = showMessageInTitle;
- }
-
-
- /* ------------------------------------------------------------ */
public boolean getShowMessageInTitle()
{
return _showMessageInTitle;
}
/* ------------------------------------------------------------ */
- protected void write(Writer writer,String string)
- throws IOException
+ /**
+ * @param showMessageInTitle whether the error message appears in page title
+ */
+ public void setShowMessageInTitle(boolean showMessageInTitle)
{
- if (string==null)
+ _showMessageInTitle = showMessageInTitle;
+ }
+
+ /* ------------------------------------------------------------ */
+ protected void write(Writer writer, String string)
+ throws IOException
+ {
+ if (string == null)
return;
writer.write(StringUtil.sanitizeXmlString(string));
@@ -291,11 +303,22 @@ public class ErrorHandler extends AbstractHandler
/* ------------------------------------------------------------ */
public static ErrorHandler getErrorHandler(Server server, ContextHandler context)
{
- ErrorHandler error_handler=null;
- if (context!=null)
- error_handler=context.getErrorHandler();
- if (error_handler==null && server!=null)
- error_handler = server.getBean(ErrorHandler.class);
+ ErrorHandler error_handler = null;
+ if (context != null)
+ error_handler = context.getErrorHandler();
+ if (error_handler == null)
+ {
+ synchronized (ErrorHandler.class)
+ {
+ error_handler = server.getBean(ErrorHandler.class);
+ if (error_handler == null)
+ {
+ error_handler = new ErrorHandler();
+ error_handler.setServer(server);
+ server.addManaged(error_handler);
+ }
+ }
+ }
return error_handler;
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
index f799b3a4f85..2a1b771d6c8 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java
@@ -28,7 +28,6 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
index c8ef17b61c7..bc60dc7c059 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
@@ -25,10 +25,8 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.LifeCycle;
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 6a38a710a84..9c2a72ab59c 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
@@ -498,11 +498,12 @@ public class ResourceHandler extends HandlerWrapper
doResponseHeaders(response,resource,mime);
if (_etags)
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG,etag);
+ if (last_modified>0)
+ response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),last_modified);
if(skipContentBody)
return;
-
// Send the content
OutputStream out =null;
try {out = response.getOutputStream();}
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 ba0d6d05a0b..8a14d13ab5f 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
@@ -18,6 +18,8 @@
package org.eclipse.jetty.server.handler.gzip;
+import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
+
import java.io.File;
import java.io.IOException;
import java.util.Set;
@@ -28,6 +30,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -61,10 +64,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
public final static String GZIP = "gzip";
public final static String DEFLATE = "deflate";
- public final static String ETAG_GZIP="--gzip";
- public final static String ETAG = "o.e.j.s.Gzip.ETag";
public final static int DEFAULT_MIN_GZIP_SIZE=16;
-
private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
private int _compressionLevel=Deflater.DEFAULT_COMPRESSION;
private boolean _checkGzExists = true;
@@ -79,6 +79,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
private HttpField _vary;
+
/* ------------------------------------------------------------ */
/**
* Instantiates a new gzip handler.
@@ -415,11 +416,19 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
}
// Special handling for etags
- String etag = request.getHeader("If-None-Match");
+ String etag = baseRequest.getHttpFields().get(HttpHeader.IF_NONE_MATCH);
if (etag!=null)
{
- if (etag.contains(ETAG_GZIP))
- request.setAttribute(ETAG,etag.replace(ETAG_GZIP,""));
+ int i=etag.indexOf(ETAG_GZIP_QUOTE);
+ if (i>0)
+ {
+ while (i>=0)
+ {
+ etag=etag.substring(0,i)+etag.substring(i+GzipHttpContent.ETAG_GZIP.length());
+ i=etag.indexOf(ETAG_GZIP_QUOTE,i);
+ }
+ baseRequest.getHttpFields().put(new HttpField(HttpHeader.IF_NONE_MATCH,etag));
+ }
}
// install interceptor and handle
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
index 41ba3b9c65c..0aa28d786b7 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@@ -41,7 +42,6 @@ import org.eclipse.jetty.util.log.Logger;
public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
{
public static Logger LOG = Log.getLogger(GzipHttpOutputInterceptor.class);
- private final static PreEncodedHttpField CONTENT_ENCODING_GZIP=new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING,"gzip");
private final static byte[] GZIP_HEADER = new byte[] { (byte)0x1f, (byte)0x8b, Deflater.DEFLATED, 0, 0, 0, 0, 0, 0, 0 };
public final static HttpField VARY_ACCEPT_ENCODING_USER_AGENT=new PreEncodedHttpField(HttpHeader.VARY,HttpHeader.ACCEPT_ENCODING+", "+HttpHeader.USER_AGENT);
@@ -202,7 +202,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
return;
}
- fields.put(CONTENT_ENCODING_GZIP);
+ fields.put(GzipHttpContent.CONTENT_ENCODING_GZIP);
_crc.reset();
_buffer=_channel.getByteBufferPool().acquire(_bufferSize,false);
BufferUtil.fill(_buffer,GZIP_HEADER,0,GZIP_HEADER.length);
@@ -213,7 +213,7 @@ public class GzipHttpOutputInterceptor implements HttpOutput.Interceptor
if (etag!=null)
{
int end = etag.length()-1;
- etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHandler.ETAG_GZIP+'"':etag+GzipHandler.ETAG_GZIP;
+ etag=(etag.charAt(end)=='"')?etag.substring(0,end)+GzipHttpContent.ETAG_GZIP+'"':etag+GzipHttpContent.ETAG_GZIP;
fields.put(HttpHeader.ETAG,etag);
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java
index 2dc6aa81956..7130060dbb3 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionDataStore.java
@@ -34,13 +34,14 @@ public interface SessionDataStore extends LifeCycle
/**
* Initialize this session data store for the
* given context. A SessionDataStore can only
- * be used by one context.
+ * be used by one context(/session manager).
*
* @param contextId
*/
void initialize(ContextId contextId);
+
/**
* Read in session data from storage
* @param id
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
index 3468b6ced80..eb464180d65 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.server;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -26,6 +29,7 @@ import java.io.PrintWriter;
import java.net.Socket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -41,9 +45,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
-
public abstract class AbstractHttpTest
{
@Rule
@@ -80,22 +81,24 @@ public abstract class AbstractHttpTest
protected SimpleHttpResponse executeRequest() throws URISyntaxException, IOException
{
- Socket socket = new Socket("localhost", connector.getLocalPort());
- socket.setSoTimeout((int)connector.getIdleTimeout());
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
- PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
+ try(Socket socket = new Socket("localhost", connector.getLocalPort());)
+ {
+ socket.setSoTimeout((int)connector.getIdleTimeout());
+ BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
+ PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
- writer.write("GET / " + httpVersion + "\r\n");
- writer.write("Host: localhost\r\n");
- writer.write("\r\n");
- writer.flush();
+ writer.write("GET / " + httpVersion + "\r\n");
+ writer.write("Host: localhost\r\n");
+ writer.write("\r\n");
+ writer.flush();
- SimpleHttpResponse response = httpParser.readResponse(reader);
- if ("HTTP/1.1".equals(httpVersion) && response.getHeaders().get("content-length") == null && response
- .getHeaders().get("transfer-encoding") == null)
- assertThat("If HTTP/1.1 response doesn't contain transfer-encoding or content-length headers, " +
- "it should contain connection:close", response.getHeaders().get("connection"), is("close"));
- return response;
+ SimpleHttpResponse response = httpParser.readResponse(reader);
+ if ("HTTP/1.1".equals(httpVersion) && response.getHeaders().get("content-length") == null && response
+ .getHeaders().get("transfer-encoding") == null)
+ assertThat("If HTTP/1.1 response doesn't contain transfer-encoding or content-length headers, " +
+ "it should contain connection:close", response.getHeaders().get("connection"), is("close"));
+ return response;
+ }
}
protected void assertResponseBody(SimpleHttpResponse response, String expectedResponseBody)
@@ -126,8 +129,14 @@ public abstract class AbstractHttpTest
this.throwException = throwException;
}
- @Override
+ @Override final
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ super.handle(target,baseRequest,request,response);
+ }
+
+ @Override
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (throwException)
throw new TestCommitException();
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 d188105fad5..087007499f5 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
@@ -18,6 +18,11 @@
package org.eclipse.jetty.server;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -42,11 +47,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import static org.hamcrest.Matchers.containsString;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
public class AsyncRequestReadTest
{
private static Server server;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelStatisticsTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorStatisticsTest.java
similarity index 98%
rename from jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelStatisticsTest.java
rename to jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorStatisticsTest.java
index 24523639330..08fb8544758 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelStatisticsTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorStatisticsTest.java
@@ -45,9 +45,9 @@ import org.junit.Ignore;
import org.junit.Test;
@Ignore("Ignored while refactoring the connection events and statistics")
-public class SelectChannelStatisticsTest
+public class ConnectorStatisticsTest
{
- private static final Logger LOG = Log.getLogger(SelectChannelStatisticsTest.class);
+ private static final Logger LOG = Log.getLogger(ConnectorStatisticsTest.class);
private static Server _server;
private static ConnectorStatistics _statistics;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
index caad44978fb..2583a0310ea 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
@@ -18,7 +18,6 @@
package org.eclipse.jetty.server;
-import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
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 2fa7fd6af68..e0a5ee44877 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
@@ -66,7 +66,7 @@ public class DumpHandler extends AbstractHandler
* @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (!isStarted())
return;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
index 7a16f503ee2..ff49a768c8b 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java
@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
@@ -60,7 +61,7 @@ public class ExtendedServerTest extends HttpServerTestBase
{
@Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
return new ExtendedEndPoint(channel,selectSet,key, getScheduler(), getIdleTimeout());
}
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 ff80487c3ca..829669c4c88 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
@@ -47,6 +47,7 @@ import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.log.AbstractLogger;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
@@ -764,7 +765,7 @@ public class HttpConnectionTest
try
{
- ((StdErrLog)Log.getLogger(HttpChannel.class)).info("Excpect IOException: Response header too large...");
+ ((AbstractLogger)Log.getLogger(HttpChannel.class)).info("Excpect IOException: Response header too large...");
((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
int offset = 0;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
index f03282beaf4..d07455b27cd 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitBadBehaviourTest.java
@@ -84,7 +84,7 @@ public class HttpManyWaysToAsyncCommitBadBehaviourTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
final CyclicBarrier resumeBarrier = new CyclicBarrier(1);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
index f4cf4a41ae7..758b0043672 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java
@@ -100,7 +100,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -118,7 +118,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
}).run();
}
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -157,7 +157,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -176,7 +176,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -215,7 +215,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -242,7 +242,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -285,7 +285,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -314,7 +314,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -356,7 +356,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -383,7 +383,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -425,7 +425,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -455,7 +455,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -500,7 +500,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -529,7 +529,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -572,7 +572,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -601,7 +601,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -645,7 +645,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -674,7 +674,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -713,7 +713,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -742,7 +742,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -782,7 +782,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, final HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (request.getAttribute(CONTEXT_ATTRIBUTE) == null)
{
@@ -811,7 +811,7 @@ public class HttpManyWaysToAsyncCommitTest extends AbstractHttpTest
}).run();
}
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java
index 3eeef861cb8..3c4f1161d2a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java
@@ -83,10 +83,10 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(false); // not needed, but lets be explicit about what the test does
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -121,10 +121,10 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -161,11 +161,11 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getWriter().write("foobar");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -206,12 +206,12 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getWriter().write("foobar");
response.flushBuffer();
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -249,11 +249,11 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.flushBuffer();
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -294,13 +294,13 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getWriter().write("foo");
response.flushBuffer();
response.getWriter().write("bar");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -369,12 +369,12 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setBufferSize(4);
response.getWriter().write("foobar");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -386,13 +386,13 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setBufferSize(8);
response.getWriter().write("fo");
response.getWriter().write("obarfoobar");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -404,7 +404,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setBufferSize(8);
@@ -414,7 +414,7 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
response.getWriter().write("fo");
response.getWriter().write("ob");
response.getWriter().write("ar");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -452,12 +452,12 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setContentLength(3);
response.getWriter().write("foo");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -495,13 +495,13 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setContentLength(3);
// Only "foo" will get written and "bar" will be discarded
response.getWriter().write("foobar");
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -539,12 +539,12 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getWriter().write("foo");
response.setContentLength(3);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
@@ -582,12 +582,12 @@ public class HttpManyWaysToCommitTest extends AbstractHttpTest
}
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getWriter().write("foobar");
response.setContentLength(3);
- super.handle(target, baseRequest, request, response);
+ super.doHandle(target, baseRequest, request, response);
}
}
}
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 57f3016bf2b..7da9552ccbb 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
@@ -54,6 +54,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.AbstractLogger;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
@@ -187,7 +188,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
{
client.setSoTimeout(10000);
((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
- ((StdErrLog) Log.getLogger(HttpConnection.class)).info("expect request is too large, then ISE extra data ...");
+ ((AbstractLogger) Log.getLogger(HttpConnection.class)).info("expect request is too large, then ISE extra data ...");
OutputStream os = client.getOutputStream();
byte[] buffer = new byte[64 * 1024];
@@ -218,7 +219,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
- ((StdErrLog)Log.getLogger(HttpConnection.class)).info("expect URI is too large, then ISE extra data ...");
+ ((AbstractLogger)Log.getLogger(HttpConnection.class)).info("expect URI is too large, then ISE extra data ...");
OutputStream os = client.getOutputStream();
byte[] buffer = new byte[64 * 1024];
@@ -244,7 +245,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
}
@Test
- public void testExceptionThrownInHandler() throws Exception
+ public void testExceptionThrownInHandlerLoop() throws Exception
{
configureServer(new AbstractHandler()
{
@@ -269,7 +270,41 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
os.flush();
String response = readResponse(client);
- assertThat("response code is 500", response.contains("500"), is(true));
+ assertThat(response,Matchers.containsString(" 500 "));
+ }
+ finally
+ {
+ ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+ }
+ }
+
+ @Test
+ public void testExceptionThrownInHandler() throws Exception
+ {
+ configureServer(new AbstractHandler()
+ {
+ @Override
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ throw new QuietServletException("TEST handler exception");
+ }
+ });
+
+ StringBuffer request = new StringBuffer("GET / HTTP/1.0\r\n");
+ request.append("Host: localhost\r\n\r\n");
+
+ Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
+ OutputStream os = client.getOutputStream();
+
+ try
+ {
+ ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+ Log.getLogger(HttpChannel.class).info("Expecting ServletException: TEST handler exception...");
+ os.write(request.toString().getBytes());
+ os.flush();
+
+ String response = readResponse(client);
+ assertThat(response,Matchers.containsString(" 500 "));
}
finally
{
@@ -285,7 +320,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
configureServer(new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
int contentLength = request.getContentLength();
@@ -299,7 +334,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
catch (EofException e)
{
earlyEOFException.set(true);
- throw e;
+ throw new QuietServletException(e);
}
if (i == 3)
fourBytesRead.set(true);
@@ -338,7 +373,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
try (Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort()))
{
((StdErrLog)Log.getLogger(HttpConnection.class)).setHideStacks(true);
- ((StdErrLog)Log.getLogger(HttpConnection.class)).info("expect header is too large, then ISE extra data ...");
+ ((AbstractLogger)Log.getLogger(HttpConnection.class)).info("expect header is too large, then ISE extra data ...");
OutputStream os = client.getOutputStream();
byte[] buffer = new byte[64 * 1024];
@@ -1180,7 +1215,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
try
{
((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
- ((StdErrLog)Log.getLogger(HttpChannel.class)).info("Expecting exception after commit then could not send 500....");
+ ((AbstractLogger)Log.getLogger(HttpChannel.class)).info("Expecting exception after commit then could not send 500....");
OutputStream os = client.getOutputStream();
InputStream is = client.getInputStream();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index bfe27e45288..abaf1cf8ad9 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -1411,7 +1411,7 @@ public class RequestTest
private String _content;
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
index a21e45d7732..67c6e2447f5 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java
@@ -49,9 +49,9 @@ public class ResourceCacheTest
Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes();
- ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false);
- ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false);
- ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false);
+ ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false,false);
+ ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false,false);
+ ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false,false);
assertEquals("1 - one", getContent(rc1, "1.txt"));
assertEquals("2 - two", getContent(rc1, "2.txt"));
@@ -79,8 +79,8 @@ public class ResourceCacheTest
Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes();
- ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false);
- ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false)
+ ResourceCache rc3 = new ResourceCache(null,r[2],mime,false,false,false);
+ ResourceCache rc2 = new ResourceCache(rc3,r[1],mime,false,false,false)
{
@Override
public boolean isCacheable(Resource resource)
@@ -89,7 +89,7 @@ public class ResourceCacheTest
}
};
- ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false);
+ ResourceCache rc1 = new ResourceCache(rc2,r[0],mime,false,false,false);
assertEquals("1 - one", getContent(rc1, "1.txt"));
assertEquals("2 - two", getContent(rc1, "2.txt"));
@@ -130,7 +130,7 @@ public class ResourceCacheTest
directory=Resource.newResource(files[0].getParentFile().getAbsolutePath());
- cache=new ResourceCache(null,directory,new MimeTypes(),false,false);
+ cache=new ResourceCache(null,directory,new MimeTypes(),false,false,false);
cache.setMaxCacheSize(95);
cache.setMaxCachedFileSize(85);
@@ -243,7 +243,7 @@ public class ResourceCacheTest
Resource[] resources = rc.getResources();
MimeTypes mime = new MimeTypes();
- ResourceCache cache = new ResourceCache(null,resources[0],mime,false,false);
+ ResourceCache cache = new ResourceCache(null,resources[0],mime,false,false,false);
assertEquals("4 - four", getContent(cache, "four.txt"));
assertEquals("4 - four (no extension)", getContent(cache, "four"));
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 41f27be3dad..b5942464891 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
@@ -403,7 +403,7 @@ public class ResponseTest
response.sendError(404);
assertEquals(404, response.getStatus());
- assertEquals(null, response.getReason());
+ assertEquals("Not Found", response.getReason());
response = newResponse();
@@ -534,6 +534,7 @@ public class ResponseTest
Response response = newResponse();
Request request = response.getHttpChannel().getRequest();
+ request.setScheme("http");
request.setAuthority(host,port);
request.setURIPathQuery("/path/info;param;jsessionid=12345?query=0&more=1#target");
request.setContextPath("/path");
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorAsyncContextTest.java
similarity index 94%
rename from jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java
rename to jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorAsyncContextTest.java
index 206cd92bcff..41bc73e6935 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelAsyncContextTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorAsyncContextTest.java
@@ -23,7 +23,7 @@ import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.util.IO;
-public class SelectChannelAsyncContextTest extends LocalAsyncContextTest
+public class ServerConnectorAsyncContextTest extends LocalAsyncContextTest
{
@Override
protected Connector initConnector()
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorCloseTest.java
similarity index 94%
rename from jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java
rename to jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorCloseTest.java
index b05561e7595..aaa6b5cf899 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelConnectorCloseTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorCloseTest.java
@@ -23,7 +23,7 @@ import org.junit.Before;
/* ------------------------------------------------------------ */
-public class SelectChannelConnectorCloseTest extends ConnectorCloseTestBase
+public class ServerConnectorCloseTest extends ConnectorCloseTestBase
{
/* ------------------------------------------------------------ */
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorHttpServerTest.java
similarity index 94%
rename from jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
rename to jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorHttpServerTest.java
index ea35c0f90ef..a94f9518bd6 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelServerTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorHttpServerTest.java
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith;
* HttpServer Tester.
*/
@RunWith(AdvancedRunner.class)
-public class SelectChannelServerTest extends HttpServerTestBase
+public class ServerConnectorHttpServerTest extends HttpServerTestBase
{
@Before
public void init() throws Exception
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
index 8ee21c5fd65..3f66131ab6a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java
@@ -18,6 +18,15 @@
package org.eclipse.jetty.server;
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@@ -33,8 +42,8 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.io.ChannelEndPoint;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
@@ -42,15 +51,6 @@ import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.IO;
import org.junit.Test;
-import static org.hamcrest.Matchers.anyOf;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
-
public class ServerConnectorTest
{
public static class ReuseInfoHandler extends AbstractHandler
@@ -61,8 +61,8 @@ public class ServerConnectorTest
response.setContentType("text/plain");
EndPoint endPoint = baseRequest.getHttpChannel().getEndPoint();
- assertThat("Endpoint",endPoint,instanceOf(ChannelEndPoint.class));
- ChannelEndPoint channelEndPoint = (ChannelEndPoint)endPoint;
+ assertThat("Endpoint",endPoint,instanceOf(SocketChannelEndPoint.class));
+ SocketChannelEndPoint channelEndPoint = (SocketChannelEndPoint)endPoint;
Socket socket = channelEndPoint.getSocket();
ServerConnector connector = (ServerConnector)baseRequest.getHttpChannel().getConnector();
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTimeoutTest.java
similarity index 98%
rename from jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
rename to jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTimeoutTest.java
index 44eeb75d17e..27e96c4d7bb 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/SelectChannelTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTimeoutTest.java
@@ -32,7 +32,7 @@ import org.eclipse.jetty.util.IO;
import org.junit.Before;
import org.junit.Test;
-public class SelectChannelTimeoutTest extends ConnectorTimeoutTest
+public class ServerConnectorTimeoutTest extends ConnectorTimeoutTest
{
@Before
public void init() throws Exception
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ThreadStarvationTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ThreadStarvationTest.java
index fa46de262f0..7179c373af2 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ThreadStarvationTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ThreadStarvationTest.java
@@ -26,26 +26,16 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
-import java.net.SocketException;
-import java.util.concurrent.Exchanger;
-import java.util.concurrent.TimeUnit;
-import javax.net.ssl.SSLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.hamcrest.Matchers;
-import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
index 27edbe74eb2..156d0af5de8 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java
@@ -85,6 +85,18 @@ public class ContextHandlerGetResourceTest
Files.createSymbolicLink(new File(docroot,"other").toPath(),new File("../transit").toPath());
Files.createSymbolicLink(transit.toPath(),otherroot.toPath());
+
+ // /web/logs -> /var/logs -> /media/internal/logs
+ // where /media/internal -> /media/internal-physical/
+ new File(docroot,"media/internal-physical/logs").mkdirs();
+ Files.createSymbolicLink(new File(docroot,"media/internal").toPath(),new File(docroot,"media/internal-physical").toPath());
+ new File(docroot,"var").mkdir();
+ Files.createSymbolicLink(new File(docroot,"var/logs").toPath(),new File(docroot,"media/internal/logs").toPath());
+ new File(docroot,"web").mkdir();
+ Files.createSymbolicLink(new File(docroot,"web/logs").toPath(),new File(docroot,"var/logs").toPath());
+ new File(docroot,"media/internal-physical/logs/file.log").createNewFile();
+
+ System.err.println("docroot="+docroot);
}
OS_ALIAS_SUPPORTED = new File(sub, "TEXTFI~1.TXT").exists();
@@ -383,6 +395,29 @@ public class ContextHandlerGetResourceTest
}
}
+
+ @Test
+ public void testSymlinkNested() throws Exception
+ {
+ Assume.assumeTrue(OS.IS_UNIX);
+
+ try
+ {
+ allowSymlinks.set(true);
+
+ final String path="/web/logs/file.log";
+
+ Resource resource=context.getResource(path);
+ assertNotNull(resource);
+ assertEquals("file.log",resource.getFile().getName());
+ assertTrue(resource.exists());
+ }
+ finally
+ {
+ allowSymlinks.set(false);
+ }
+
+ }
@Test
public void testSymlinkUnknown() throws Exception
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java
new file mode 100644
index 00000000000..f3ae33d9d0e
--- /dev/null
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DebugHandlerTest.java
@@ -0,0 +1,182 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.util.concurrent.Executor;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManagerFactory;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
+import org.eclipse.jetty.io.MappedByteBufferPool;
+import org.eclipse.jetty.server.AbstractConnectionFactory;
+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.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.SimpleRequest;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Scheduler;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DebugHandlerTest
+{
+ public final static HostnameVerifier __hostnameverifier = new HostnameVerifier()
+ {
+ public boolean verify(String hostname, SSLSession session)
+ {
+ return true;
+ }
+ };
+
+ private SSLContext sslContext;
+ private Server server;
+ private URI serverURI;
+ private URI secureServerURI;
+
+ @SuppressWarnings("deprecation")
+ private DebugHandler debugHandler;
+ private ByteArrayOutputStream capturedLog;
+
+ @SuppressWarnings("deprecation")
+ @Before
+ public void startServer() throws Exception
+ {
+ server = new Server();
+
+ ServerConnector httpConnector = new ServerConnector(server);
+ httpConnector.setPort(0);
+ server.addConnector(httpConnector);
+
+ File keystorePath = MavenTestingUtils.getTestResourceFile("keystore");
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.setKeyStorePath(keystorePath.getAbsolutePath());
+ sslContextFactory.setKeyStorePassword("storepwd");
+ sslContextFactory.setKeyManagerPassword("keypwd");
+ sslContextFactory.setTrustStorePath(keystorePath.getAbsolutePath());
+ sslContextFactory.setTrustStorePassword("storepwd");
+ ByteBufferPool pool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
+ ServerConnector sslConnector = new ServerConnector(server,
+ (Executor)null,
+ (Scheduler)null, pool, 1, 1,
+ AbstractConnectionFactory.getFactories(sslContextFactory,new HttpConnectionFactory()));
+
+ server.addConnector(sslConnector);
+
+ debugHandler = new DebugHandler();
+ capturedLog = new ByteArrayOutputStream();
+ debugHandler.setOutputStream(capturedLog);
+ debugHandler.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ baseRequest.setHandled(true);
+ response.setStatus(HttpStatus.OK_200);
+ }
+ });
+ server.setHandler(debugHandler);
+ server.start();
+
+ String host = httpConnector.getHost();
+ if(host == null) host = "localhost";
+
+ serverURI = URI.create(String.format("http://%s:%d/", host, httpConnector.getLocalPort()));
+ secureServerURI = URI.create(String.format("https://%s:%d/", host, sslConnector.getLocalPort()));
+
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ try (InputStream stream = sslContextFactory.getKeyStoreResource().getInputStream())
+ {
+ keystore.load(stream, "storepwd".toCharArray());
+ }
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(keystore);
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
+
+ try
+ {
+ HttpsURLConnection.setDefaultHostnameVerifier(__hostnameverifier);
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ @After
+ public void stopServer() throws Exception
+ {
+ server.stop();
+ }
+
+ @Test
+ public void testThreadName() throws IOException
+ {
+ SimpleRequest req = new SimpleRequest(serverURI);
+ req.getString("/foo/bar?a=b");
+
+ String log = capturedLog.toString(StandardCharsets.UTF_8.name());
+ String expectedThreadName = String.format("//%s:%s/foo/bar?a=b",serverURI.getHost(),serverURI.getPort());
+ assertThat("ThreadName", log, containsString(expectedThreadName));
+ // Look for bad/mangled/duplicated schemes
+ assertThat("ThreadName", log, not(containsString("http:"+expectedThreadName)));
+ assertThat("ThreadName", log, not(containsString("https:"+expectedThreadName)));
+ }
+
+ @Test
+ public void testSecureThreadName() throws IOException
+ {
+ SimpleRequest req = new SimpleRequest(secureServerURI);
+ req.getString("/foo/bar?a=b");
+
+ String log = capturedLog.toString(StandardCharsets.UTF_8.name());
+ String expectedThreadName = String.format("https://%s:%s/foo/bar?a=b",secureServerURI.getHost(),secureServerURI.getPort());
+ assertThat("ThreadName", log, containsString(expectedThreadName));
+ // Look for bad/mangled/duplicated schemes
+ assertThat("ThreadName", log, not(containsString("http:"+expectedThreadName)));
+ assertThat("ThreadName", log, not(containsString("https:"+expectedThreadName)));
+ }
+}
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogTest.java
index 21ff6f04612..61566775ddb 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/RequestLogTest.java
@@ -66,6 +66,7 @@ public class RequestLogTest
_server.stop();
}
+
@Test
public void testNotHandled() throws Exception
{
@@ -73,6 +74,30 @@ public class RequestLogTest
String log = _log.exchange(null,5,TimeUnit.SECONDS);
assertThat(log,containsString("GET /foo HTTP/1.0\" 404 "));
}
+
+ @Test
+ public void testRequestLine() throws Exception
+ {
+ _connector.getResponses("GET /foo?data=1 HTTP/1.0\nhost: host:80\n\n");
+ String log = _log.exchange(null,5,TimeUnit.SECONDS);
+ // TODO should be without host (https://bugs.eclipse.org/bugs/show_bug.cgi?id=480276)
+ // assertThat(log,containsString("GET /foo?data=1 HTTP/1.0\" 200 "));
+ assertThat(log,containsString("GET //host:80/foo?data=1 HTTP/1.0\" 200 "));
+
+ _connector.getResponses("GET //host/foo?data=1 HTTP/1.0\n\n");
+ log = _log.exchange(null,5,TimeUnit.SECONDS);
+ assertThat(log,containsString("GET //host/foo?data=1 HTTP/1.0\" 200 "));
+
+ _connector.getResponses("GET //absolute:80/foo?data=1 HTTP/1.0\nhost: host:80\n\n");
+ log = _log.exchange(null,5,TimeUnit.SECONDS);
+ // TODO should it be with absolute? (https://bugs.eclipse.org/bugs/show_bug.cgi?id=480276)
+ // assertThat(log,containsString("GET //absolute:80/foo?data=1 HTTP/1.0\" 200 "));
+ assertThat(log,containsString("GET //host:80/foo?data=1 HTTP/1.0\" 200 "));
+
+ _connector.getResponses("GET http://host:80/foo?data=1 HTTP/1.0\n\n");
+ log = _log.exchange(null,5,TimeUnit.SECONDS);
+ assertThat(log,containsString("GET http://host:80/foo?data=1 HTTP/1.0\" 200 "));
+ }
@Test
public void testSmallData() 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 7101ad4ed5e..22d28d72d39 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,9 @@
package org.eclipse.jetty.server.handler;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -33,6 +36,7 @@ import java.nio.file.Files;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
+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;
@@ -58,6 +62,7 @@ public class ResourceHandlerTest
private static Server _server;
private static HttpConfiguration _config;
private static ServerConnector _connector;
+ private static LocalConnector _local;
private static ContextHandler _contextHandler;
private static ResourceHandler _resourceHandler;
@@ -111,7 +116,9 @@ public class ResourceHandlerTest
_config.setOutputBufferSize(2048);
_connector = new ServerConnector(_server,new HttpConnectionFactory(_config));
- _server.setConnectors(new Connector[] { _connector });
+ _local = new LocalConnector(_server);
+
+ _server.setConnectors(new Connector[] { _connector, _local });
_resourceHandler = new ResourceHandler();
_resourceHandler.setMinAsyncContentLength(4096);
@@ -151,6 +158,18 @@ public class ResourceHandlerTest
Assert.assertEquals("simple text",sr.getString("/resource/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"));
+ }
+
@Test
public void testBigFile() throws Exception
{
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 a242a51151e..f33d45d1f10 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
@@ -20,7 +20,6 @@ package org.eclipse.jetty.server.ssl;
import static org.junit.Assert.assertEquals;
-import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
index 5bdb81b386e..062c79c42ac 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SniSslConnectionFactoryTest.java
@@ -18,6 +18,10 @@
package org.eclipse.jetty.server.ssl;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertThat;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -98,7 +102,7 @@ public class SniSslConnectionFactoryTest
_server.setHandler(new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(200);
@@ -237,8 +241,8 @@ public class SniSslConnectionFactoryTest
output.flush();
response = response(input);
- Assert.assertTrue(response.startsWith("HTTP/1.1 400 "));
- Assert.assertThat(response, Matchers.containsString("Host does not match SNI"));
+ assertThat(response,startsWith("HTTP/1.1 400 "));
+ assertThat(response, containsString("Host does not match SNI"));
}
finally
{
diff --git a/jetty-server/src/test/resources/jetty-logging.properties b/jetty-server/src/test/resources/jetty-logging.properties
index adf68c7c337..f345cd6cb5e 100644
--- a/jetty-server/src/test/resources/jetty-logging.properties
+++ b/jetty-server/src/test/resources/jetty-logging.properties
@@ -1,3 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
-#org.eclipse.jetty.LEVEL=DEBUG
+org.eclipse.jetty.LEVEL=INFO
#org.eclipse.jetty.server.LEVEL=DEBUG
diff --git a/jetty-servlet/pom.xml b/jetty-servlet/pom.xml
index a1ae6875f21..cc6fb609116 100644
--- a/jetty-servlet/pom.xml
+++ b/jetty-servlet/pom.xml
@@ -3,7 +3,7 @@
jetty-projectorg.eclipse.jetty
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-servlet
@@ -15,24 +15,6 @@
-
- org.apache.felix
- maven-bundle-plugin
- true
-
-
-
- manifest
-
-
-
- javax.servlet.*;version="[2.6.0,3.2)",org.eclipse.jetty.jmx.*;resolution:=optional,*
- <_nouses>true
-
-
-
-
- org.apache.maven.pluginsmaven-jar-plugin
@@ -66,10 +48,23 @@
${project.version}true
+
+ org.eclipse.jetty
+ apache-jsp
+ ${project.version}
+ test
+ org.eclipse.jetty.toolchainjetty-test-helpertest
+
+ org.eclipse.jetty
+ jetty-http
+ ${project.version}
+ tests
+ test
+
diff --git a/jetty-servlet/src/main/config/modules/servlet.mod b/jetty-servlet/src/main/config/modules/servlet.mod
index fdb65c57a16..f21e18a87e7 100644
--- a/jetty-servlet/src/main/config/modules/servlet.mod
+++ b/jetty-servlet/src/main/config/modules/servlet.mod
@@ -1,6 +1,5 @@
-#
-# Jetty Servlet Module
-#
+[description]
+Enables standard Servlet handling.
[depend]
server
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
index 5455b4ad515..9d3f854b118 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/BaseHolder.java
@@ -92,7 +92,7 @@ public abstract class BaseHolder extends AbstractLifeCycle implements Dumpabl
{
try
{
- _class=Loader.loadClass(Holder.class, _className);
+ _class=Loader.loadClass(_className);
if(LOG.isDebugEnabled())
LOG.debug("Holding {} from {}",_class,_class.getClassLoader());
}
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
index 1942359252c..be9e81423fc 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
@@ -18,6 +18,9 @@
package org.eclipse.jetty.servlet;
+import static org.eclipse.jetty.http.GzipHttpContent.ETAG_GZIP_QUOTE;
+import static org.eclipse.jetty.http.GzipHttpContent.removeGzipFromETag;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -40,6 +43,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.DateParser;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
@@ -54,6 +58,7 @@ import org.eclipse.jetty.server.HttpOutput;
import org.eclipse.jetty.server.InclusiveByteRange;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ResourceCache;
+import org.eclipse.jetty.server.ResourceContentFactory;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.BufferUtil;
@@ -161,6 +166,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
private Resource _resourceBase;
private ResourceCache _cache;
+ private HttpContent.Factory _contentFactory;
private MimeTypes _mimeTypes;
private String[] _welcomes;
@@ -243,7 +249,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
String cc=getInitParameter("cacheControl");
if (cc!=null)
_cacheControl=new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, cc);
-
+
String resourceCache = getInitParameter("resourceCache");
int max_cache_size=getInitInt("maxCacheSize", -2);
int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
@@ -257,23 +263,23 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
_cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
if (LOG.isDebugEnabled())
- LOG.debug("Cache {}={}",resourceCache,_cache);
+ LOG.debug("Cache {}={}",resourceCache,_contentFactory);
}
_etags = getInitBoolean("etags",_etags);
-
+
try
{
if (_cache==null && (max_cached_files!=-2 || max_cache_size!=-2 || max_cached_file_size!=-2))
{
- _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
-
+ _cache = new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags,_gzip);
if (max_cache_size>=0)
_cache.setMaxCacheSize(max_cache_size);
if (max_cached_file_size>=-1)
_cache.setMaxCachedFileSize(max_cached_file_size);
if (max_cached_files>=-1)
_cache.setMaxCachedFiles(max_cached_files);
+ _servletContext.setAttribute(resourceCache==null?"resourceCache":resourceCache,_cache);
}
}
catch (Exception e)
@@ -281,33 +287,34 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
LOG.warn(Log.EXCEPTION,e);
throw new UnavailableException(e.toString());
}
-
- _gzipEquivalentFileExtensions = new ArrayList();
- String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
- if (otherGzipExtensions != null)
- {
- //comma separated list
- StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
- while (tok.hasMoreTokens())
- {
- String s = tok.nextToken().trim();
- _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
- }
- }
- else
- {
- //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
- _gzipEquivalentFileExtensions.add(".svgz");
- }
- _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
- for (ServletHolder h :_servletHandler.getServlets())
- if (h.getServletInstance()==this)
- _defaultHolder=h;
+ _contentFactory=_cache==null?new ResourceContentFactory(this,_mimeTypes,-1,_gzip):_cache; // TODO pass a buffer size
+ _gzipEquivalentFileExtensions = new ArrayList();
+ String otherGzipExtensions = getInitParameter("otherGzipFileExtensions");
+ if (otherGzipExtensions != null)
+ {
+ //comma separated list
+ StringTokenizer tok = new StringTokenizer(otherGzipExtensions,",",false);
+ while (tok.hasMoreTokens())
+ {
+ String s = tok.nextToken().trim();
+ _gzipEquivalentFileExtensions.add((s.charAt(0)=='.'?s:"."+s));
+ }
+ }
+ else
+ {
+ //.svgz files are gzipped svg files and must be served with Content-Encoding:gzip
+ _gzipEquivalentFileExtensions.add(".svgz");
+ }
- if (LOG.isDebugEnabled())
- LOG.debug("resource base = "+_resourceBase);
+ _servletHandler= _contextHandler.getChildHandlerByClass(ServletHandler.class);
+ for (ServletHolder h :_servletHandler.getServlets())
+ if (h.getServletInstance()==this)
+ _defaultHolder=h;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("resource base = "+_resourceBase);
}
/**
@@ -422,8 +429,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
String servletPath=null;
String pathInfo=null;
Enumeration reqRanges = null;
- Boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
- if (included!=null && included.booleanValue())
+ boolean included =request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI)!=null;
+ if (included)
{
servletPath=(String)request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
pathInfo=(String)request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
@@ -435,7 +442,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
else
{
- included = Boolean.FALSE;
servletPath = _pathInfoOnly?"/":request.getServletPath();
pathInfo = request.getPathInfo();
@@ -447,155 +453,72 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
-
-
- // Find the resource and content
- Resource resource=null;
+ boolean gzippable=_gzip && !endsWithSlash && !included && reqRanges==null;
+
HttpContent content=null;
- boolean close_content=true;
+ boolean release_content=true;
try
{
- // is gzip enabled?
- String pathInContextGz=null;
- boolean gzip=false;
- if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
- {
- // Look for a gzip resource
- pathInContextGz=pathInContext+".gz";
- if (_cache==null)
- resource=getResource(pathInContextGz);
- else
- {
- content=_cache.lookup(pathInContextGz);
- resource=(content==null)?null:content.getResource();
- }
-
- // Does a gzip resource exist?
- if (resource!=null && resource.exists() && !resource.isDirectory())
- {
- // Tell caches that response may vary by accept-encoding
- response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
-
- // Does the client accept gzip?
- String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
- if (accept!=null && accept.indexOf("gzip")>=0)
- gzip=true;
- }
- }
-
- // find resource
- if (!gzip)
- {
- if (_cache==null)
- resource=getResource(pathInContext);
- else
- {
- content=_cache.lookup(pathInContext);
- resource=content==null?null:content.getResource();
- }
- }
-
+ // Find the content
+ content=_contentFactory.getContent(pathInContext);
if (LOG.isDebugEnabled())
- LOG.debug(String.format("uri=%s, resource=%s, content=%s",request.getRequestURI(),resource,content));
-
- // Handle resource
- if (resource==null || !resource.exists())
+ LOG.info("content={}",content);
+
+ // Not found?
+ if (content==null || !content.getResource().exists())
{
if (included)
throw new FileNotFoundException("!" + pathInContext);
response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
}
- else if (!resource.isDirectory())
+
+ // Directory?
+ if (content.getResource().isDirectory())
{
- if (endsWithSlash && pathInContext.length()>1)
- {
- String q=request.getQueryString();
- pathInContext=pathInContext.substring(0,pathInContext.length()-1);
- if (q!=null&&q.length()!=0)
- pathInContext+="?"+q;
- response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
- }
- else
- {
- // ensure we have content
- if (content==null)
- content=new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(pathInContext),response.getBufferSize(),_etags);
-
- if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
- {
- if (gzip || isGzippedContent(pathInContext))
- {
- response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
- String mt=_servletContext.getMimeType(pathInContext);
- if (mt!=null)
- response.setContentType(mt);
- }
- close_content=sendData(request,response,included.booleanValue(),resource,content,reqRanges);
- }
- }
+ sendWelcome(content,pathInContext,endsWithSlash,included,request,response);
+ return;
}
- else
+
+ // Strip slash?
+ if (endsWithSlash && pathInContext.length()>1)
{
- String welcome=null;
-
- if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
- {
- StringBuffer buf=request.getRequestURL();
- synchronized(buf)
- {
- int param=buf.lastIndexOf(";");
- if (param<0)
- buf.append('/');
- else
- buf.insert(param,'/');
- String q=request.getQueryString();
- if (q!=null&&q.length()!=0)
- {
- buf.append('?');
- buf.append(q);
- }
- response.setContentLength(0);
- response.sendRedirect(response.encodeRedirectURL(buf.toString()));
- }
- }
- // else look for a welcome file
- else if (null!=(welcome=getWelcomeFile(pathInContext)))
+ String q=request.getQueryString();
+ pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+ if (q!=null&&q.length()!=0)
+ pathInContext+="?"+q;
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
+ return;
+ }
+
+ // Conditional response?
+ if (!included && !passConditionalHeaders(request,response,content))
+ return;
+
+ // Gzip?
+ HttpContent gzip_content = gzippable?content.getGzipContent():null;
+ if (gzip_content!=null)
+ {
+ // Tell caches that response may vary by accept-encoding
+ response.addHeader(HttpHeader.VARY.asString(),HttpHeader.ACCEPT_ENCODING.asString());
+
+ // Does the client accept gzip?
+ String accept=request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+ if (accept!=null && accept.indexOf("gzip")>=0)
{
if (LOG.isDebugEnabled())
- LOG.debug("welcome={}",welcome);
- if (_redirectWelcome)
- {
- // Redirect to the index
- response.setContentLength(0);
- String q=request.getQueryString();
- if (q!=null&&q.length()!=0)
- response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
- else
- response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
- }
- else
- {
- // Forward to the index
- RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
- if (dispatcher!=null)
- {
- if (included.booleanValue())
- dispatcher.include(request,response);
- else
- {
- request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
- dispatcher.forward(request,response);
- }
- }
- }
- }
- else
- {
- content=new ResourceHttpContent(resource,_mimeTypes.getMimeByExtension(pathInContext),_etags);
- if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
- sendDirectory(request,response,resource,pathInContext);
+ LOG.debug("gzip={}",gzip_content);
+ content=gzip_content;
}
}
+
+ // TODO this should be done by HttpContent#getContentEncoding
+ if (isGzippedContent(pathInContext))
+ response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),"gzip");
+
+ // Send the data
+ release_content=sendData(request,response,included,content,reqRanges);
+
}
catch(IllegalArgumentException e)
{
@@ -605,17 +528,80 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
finally
{
- if (close_content)
+ if (release_content)
{
if (content!=null)
content.release();
- else if (resource!=null)
- resource.close();
}
}
}
+ protected void sendWelcome(HttpContent content, String pathInContext, boolean endsWithSlash, boolean included, HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException
+ {
+ // Redirect to directory
+ if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+ {
+ StringBuffer buf=request.getRequestURL();
+ synchronized(buf)
+ {
+ int param=buf.lastIndexOf(";");
+ if (param<0)
+ buf.append('/');
+ else
+ buf.insert(param,'/');
+ String q=request.getQueryString();
+ if (q!=null&&q.length()!=0)
+ {
+ buf.append('?');
+ buf.append(q);
+ }
+ response.setContentLength(0);
+ response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+ }
+ return;
+ }
+
+ // look for a welcome file
+ String welcome=getWelcomeFile(pathInContext);
+ if (welcome!=null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("welcome={}",welcome);
+ if (_redirectWelcome)
+ {
+ // Redirect to the index
+ response.setContentLength(0);
+ String q=request.getQueryString();
+ if (q!=null&&q.length()!=0)
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
+ else
+ response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.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;
+ }
+
+ if (included || passConditionalHeaders(request,response, content))
+ sendDirectory(request,response,content.getResource(),pathInContext);
+ }
+
+ /* ------------------------------------------------------------ */
protected boolean isGzippedContent(String path)
{
if (path == null) return false;
@@ -699,7 +685,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
/* ------------------------------------------------------------ */
/* Check modification date headers.
*/
- protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
+ protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, HttpContent content)
throws IOException
{
try
@@ -711,6 +697,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (request instanceof Request)
{
+ // Find multiple fields by iteration as an optimization
HttpFields fields = ((Request)request).getHttpFields();
for (int i=fields.size();i-->0;)
{
@@ -748,16 +735,17 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{
if (_etags)
{
+ String etag=content.getETagValue();
if (ifm!=null)
{
boolean match=false;
- if (content.getETagValue()!=null)
+ if (etag!=null)
{
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
while (!match && quoted.hasMoreTokens())
{
String tag = quoted.nextToken();
- if (content.getETagValue().equals(tag))
+ if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
match=true;
}
}
@@ -769,33 +757,25 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
}
- if (ifnm!=null && content.getETagValue()!=null)
+ if (ifnm!=null && etag!=null)
{
- // Look for Gzip'd version of etag
- if (content.getETagValue().equals(request.getAttribute("o.e.j.s.Gzip.ETag")))
+ // Handle special case of exact match OR gzip exact match
+ if (etag.equals(ifnm) || ifnm.endsWith(ETAG_GZIP_QUOTE) && ifnm.indexOf(',')<0 && etag.equals(removeGzipFromETag(etag)))
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.setHeader(HttpHeader.ETAG.asString(),ifnm);
return false;
}
- // Handle special case of exact match.
- if (content.getETagValue().equals(ifnm))
- {
- response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
- return false;
- }
-
// Handle list of tags
QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
while (quoted.hasMoreTokens())
{
String tag = quoted.nextToken();
- if (content.getETagValue().equals(tag))
+ if (etag.equals(tag) || tag.endsWith(ETAG_GZIP_QUOTE) && etag.equals(removeGzipFromETag(tag)))
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- response.setHeader(HttpHeader.ETAG.asString(),content.getETagValue());
+ response.setHeader(HttpHeader.ETAG.asString(),tag);
return false;
}
}
@@ -820,7 +800,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
long ifmsl=request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
- if (ifmsl!=-1 && resource.lastModified()/1000 <= ifmsl/1000)
+ if (ifmsl!=-1 && content.getResource().lastModified()/1000 <= ifmsl/1000)
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
if (_etags)
@@ -831,7 +811,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
// Parse the if[un]modified dates and compare to resource
- if (ifums!=-1 && resource.lastModified()/1000 > ifums/1000)
+ if (ifums!=-1 && content.getResource().lastModified()/1000 > ifums/1000)
{
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return false;
@@ -894,12 +874,11 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
protected boolean sendData(HttpServletRequest request,
HttpServletResponse response,
boolean include,
- Resource resource,
final HttpContent content,
Enumeration reqRanges)
throws IOException
{
- final long content_length = (content==null)?resource.length():content.getContentLengthValue();
+ final long content_length = content.getContentLengthValue();
// Get the output stream (or writer)
OutputStream out =null;
@@ -908,7 +887,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{
out = response.getOutputStream();
- // has a filter already written to the response?
+ // has something already written to the response?
written = out instanceof HttpOutput
? ((HttpOutput)out).isWritten()
: true;
@@ -928,18 +907,18 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (include)
{
// write without headers
- resource.writeTo(out,0,content_length);
+ content.getResource().writeTo(out,0,content_length);
}
// else if we can't do a bypass write because of wrapping
- else if (content==null || written || !(out instanceof HttpOutput))
+ else if (written || !(out instanceof HttpOutput))
{
// write normally
putHeaders(response,content,written?-1:0);
- ByteBuffer buffer = (content==null)?null:content.getIndirectBuffer();
+ ByteBuffer buffer = content.getIndirectBuffer();
if (buffer!=null)
BufferUtil.writeTo(buffer,out);
else
- resource.writeTo(out,0,content_length);
+ content.getResource().writeTo(out,0,content_length);
}
// else do a bypass write
else
@@ -983,7 +962,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
}
// otherwise write content blocking
((HttpOutput)out).sendContent(content);
-
}
}
else
@@ -998,7 +976,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
InclusiveByteRange.to416HeaderRangeString(content_length));
- resource.writeTo(out,0,content_length);
+ content.getResource().writeTo(out,0,content_length);
return true;
}
@@ -1014,7 +992,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
response.addDateHeader(HttpHeader.DATE.asString(),System.currentTimeMillis());
response.setHeader(HttpHeader.CONTENT_RANGE.asString(),
singleSatisfiableRange.toHeaderRangeString(content_length));
- resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+ content.getResource().writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
return true;
}
@@ -1041,7 +1019,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
ctp = "multipart/byteranges; boundary=";
response.setContentType(ctp+multi.getBoundary());
- InputStream in=resource.getInputStream();
+ InputStream in=content.getResource().getInputStream();
long pos=0;
// calculate the content-length
@@ -1075,7 +1053,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (start _errorPages= new HashMap(); // code or exception to URL
- private final List _errorPageList=new ArrayList(); // list of ErrorCode by range
+ private final Map _errorPages= new HashMap<>(); // code or exception to URL
+ private final List _errorPageList= new ArrayList<>(); // list of ErrorCode by range
- /* ------------------------------------------------------------ */
- public ErrorPageErrorHandler()
- {}
-
- /* ------------------------------------------------------------ */
@Override
public String getErrorPage(HttpServletRequest request)
{
String error_page= null;
- Throwable th= (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
+ Throwable th = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
// Walk the cause hierarchy
while (error_page == null && th != null )
{
Class> exClass=th.getClass();
- error_page= (String)_errorPages.get(exClass.getName());
+ error_page = _errorPages.get(exClass.getName());
// walk the inheritance hierarchy
while (error_page == null)
@@ -70,7 +62,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
exClass= exClass.getSuperclass();
if (exClass==null)
break;
- error_page= (String)_errorPages.get(exClass.getName());
+ error_page=_errorPages.get(exClass.getName());
}
th=(th instanceof ServletException)?((ServletException)th).getRootCause():null;
@@ -82,7 +74,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
Integer code=(Integer)request.getAttribute(Dispatcher.ERROR_STATUS_CODE);
if (code!=null)
{
- error_page= (String)_errorPages.get(Integer.toString(code));
+ error_page=_errorPages.get(Integer.toString(code));
// if still not found
if ((error_page == null) && (_errorPageList != null))
@@ -90,7 +82,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
// look for an error code range match.
for (int i = 0; i < _errorPageList.size(); i++)
{
- ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
+ ErrorCodeRange errCode = _errorPageList.get(i);
if (errCode.isInRange(code))
{
error_page = errCode.getUri();
@@ -101,26 +93,20 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
}
}
- //try servlet 3.x global error page
+ // Try servlet 3.x global error page.
if (error_page == null)
error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
-
+
return error_page;
}
-
- /* ------------------------------------------------------------ */
- /**
- * @return Returns the errorPages.
- */
public Map getErrorPages()
{
return _errorPages;
}
- /* ------------------------------------------------------------ */
/**
- * @param errorPages The errorPages to set. A map of Exception class name or error code as a string to URI string
+ * @param errorPages a map of Exception class names or error codes as a string to URI string
*/
public void setErrorPages(Map errorPages)
{
@@ -129,10 +115,11 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
_errorPages.putAll(errorPages);
}
- /* ------------------------------------------------------------ */
- /** Add Error Page mapping for an exception class
+ /**
+ * Adds ErrorPage mapping for an exception class.
* This method is called as a result of an exception-type element in a web.xml file
* or may be called directly
+ *
* @param exception The exception
* @param uri The URI of the error page.
*/
@@ -141,10 +128,11 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
_errorPages.put(exception.getName(),uri);
}
- /* ------------------------------------------------------------ */
- /** Add Error Page mapping for an exception class
+ /**
+ * Adds ErrorPage mapping for an exception class.
* This method is called as a result of an exception-type element in a web.xml file
* or may be called directly
+ *
* @param exceptionClassName The exception
* @param uri The URI of the error page.
*/
@@ -153,10 +141,11 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
_errorPages.put(exceptionClassName,uri);
}
- /* ------------------------------------------------------------ */
- /** Add Error Page mapping for a status code.
+ /**
+ * Adds ErrorPage mapping for a status code.
* This method is called as a result of an error-code element in a web.xml file
- * or may be called directly
+ * or may be called directly.
+ *
* @param code The HTTP status code to match
* @param uri The URI of the error page.
*/
@@ -165,10 +154,10 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
_errorPages.put(Integer.toString(code),uri);
}
- /* ------------------------------------------------------------ */
- /** Add Error Page mapping for a status code range.
- * This method is not available from web.xml and must be called
- * directly.
+ /**
+ * Adds ErrorPage mapping for a status code range.
+ * This method is not available from web.xml and must be called directly.
+ *
* @param from The lowest matching status code
* @param to The highest matching status code
* @param uri The URI of the error page.
@@ -178,7 +167,6 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
_errorPageList.add(new ErrorCodeRange(from, to, uri));
}
- /* ------------------------------------------------------------ */
@Override
protected void doStart() throws Exception
{
@@ -186,9 +174,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
_servletContext=ContextHandler.getCurrentContext();
}
- /* ------------------------------------------------------------ */
- /* ------------------------------------------------------------ */
- private class ErrorCodeRange
+ private static class ErrorCodeRange
{
private int _from;
private int _to;
@@ -207,12 +193,7 @@ public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.
boolean isInRange(int value)
{
- if ((value >= _from) && (value <= _to))
- {
- return true;
- }
-
- return false;
+ return (value >= _from) && (value <= _to);
}
String getUri()
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java
index 897818510e7..bdcacd123ad 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/FilterMapping.java
@@ -192,6 +192,23 @@ public class FilterMapping implements Dumpable
}
}
+ /* ------------------------------------------------------------ */
+ public EnumSet getDispatcherTypes()
+ {
+ EnumSet dispatcherTypes = EnumSet.noneOf(DispatcherType.class);
+ if ((_dispatches & ERROR) == ERROR)
+ dispatcherTypes.add(DispatcherType.ERROR);
+ if ((_dispatches & FORWARD) == FORWARD)
+ dispatcherTypes.add(DispatcherType.FORWARD);
+ if ((_dispatches & INCLUDE) == INCLUDE)
+ dispatcherTypes.add(DispatcherType.INCLUDE);
+ if ((_dispatches & REQUEST) == REQUEST)
+ dispatcherTypes.add(DispatcherType.REQUEST);
+ if ((_dispatches & ASYNC) == ASYNC)
+ dispatcherTypes.add(DispatcherType.ASYNC);
+ return dispatcherTypes;
+ }
+
/* ------------------------------------------------------------ */
/**
* @param dispatches The dispatches to set.
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
index f79803c8035..da6dd7634c6 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
@@ -421,7 +421,7 @@ public class ServletContextHandler extends ContextHandler
*/
public ServletHolder addServlet(Class extends Servlet> servlet,String pathSpec)
{
- return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
+ return getServletHandler().addServletWithMapping(servlet,pathSpec);
}
/* ------------------------------------------------------------ */
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
index b008dd72d0d..521468dd61b 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -45,19 +45,13 @@ import javax.servlet.ServletRegistration;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletSecurityElement;
-import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.PathMap;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.SecurityHandler;
-import org.eclipse.jetty.server.QuietServletException;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ServletRequestHttpWrapper;
import org.eclipse.jetty.server.ServletResponseHttpWrapper;
@@ -76,7 +70,7 @@ import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-/**
+/**
* Servlet HttpHandler.
*
* This handler maps requests to servlets that implement the
@@ -155,7 +149,7 @@ public class ServletHandler extends ScopedHandler
updateNameMappings();
updateMappings();
- if (getServletMapping("/")==null && _ensureDefaultServlet)
+ if (getServletMapping("/")==null && isEnsureDefaultServlet())
{
if (LOG.isDebugEnabled())
LOG.debug("Adding Default404Servlet to {}",this);
@@ -164,19 +158,19 @@ public class ServletHandler extends ScopedHandler
getServletMapping("/").setDefault(true);
}
- if(_filterChainsCached)
+ if (isFilterChainsCached())
{
- _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap();
- _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap();
- _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap();
- _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap();
- _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap();
+ _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<>();
+ _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<>();
+ _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<>();
+ _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<>();
+ _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<>();
- _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue();
- _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue();
- _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue();
- _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue();
- _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue();
+ _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<>();
+ _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<>();
+ _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<>();
+ _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<>();
+ _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<>();
}
if (_contextHandler==null)
@@ -226,8 +220,8 @@ public class ServletHandler extends ScopedHandler
super.doStop();
// Stop filters
- List filterHolders = new ArrayList();
- List filterMappings = ArrayUtil.asMutableList(_filterMappings);
+ List filterHolders = new ArrayList<>();
+ List filterMappings = ArrayUtil.asMutableList(_filterMappings);
if (_filters!=null)
{
for (int i=_filters.length; i-->0;)
@@ -270,7 +264,7 @@ public class ServletHandler extends ScopedHandler
_matchBeforeIndex = -1;
// Stop servlets
- List servletHolders = new ArrayList(); //will be remaining servlets
+ List servletHolders = new ArrayList<>(); //will be remaining servlets
List servletMappings = ArrayUtil.asMutableList(_servletMappings); //will be remaining mappings
if (_servlets!=null)
{
@@ -312,7 +306,7 @@ public class ServletHandler extends ScopedHandler
_servletMappings = sms;
//Retain only Listeners added via jetty apis (is Source.EMBEDDED)
- List listenerHolders = new ArrayList();
+ List listenerHolders = new ArrayList<>();
if (_listeners != null)
{
for (int i=_listeners.length; i-->0;)
@@ -345,29 +339,12 @@ public class ServletHandler extends ScopedHandler
return _identityService;
}
- /* ------------------------------------------------------------ */
- /**
- * @return Returns the contextLog.
- */
- public Object getContextLog()
- {
- return null;
- }
-
- /* ------------------------------------------------------------ */
- /**
- * @return Returns the filterMappings.
- */
@ManagedAttribute(value="filters", readonly=true)
public FilterMapping[] getFilterMappings()
{
return _filterMappings;
}
- /* ------------------------------------------------------------ */
- /** Get Filters.
- * @return Array of defined servlets
- */
@ManagedAttribute(value="filters", readonly=true)
public FilterHolder[] getFilters()
{
@@ -375,7 +352,9 @@ public class ServletHandler extends ScopedHandler
}
/* ------------------------------------------------------------ */
- /** ServletHolder matching path.
+ /**
+ * ServletHolder matching path.
+ *
* @param pathInContext Path within _context.
* @return PathMap Entries pathspec to ServletHolder
*/
@@ -393,9 +372,6 @@ public class ServletHandler extends ScopedHandler
}
/* ------------------------------------------------------------ */
- /**
- * @return Returns the servletMappings.
- */
@ManagedAttribute(value="mappings of servlets", readonly=true)
public ServletMapping[] getServletMappings()
{
@@ -413,7 +389,7 @@ public class ServletHandler extends ScopedHandler
{
if (pathSpec == null || _servletMappings == null)
return null;
-
+
ServletMapping mapping = null;
for (int i=0; i<_servletMappings.length && mapping == null; i++)
{
@@ -432,24 +408,18 @@ public class ServletHandler extends ScopedHandler
}
return mapping;
}
-
- /* ------------------------------------------------------------ */
- /** Get Servlets.
- * @return Array of defined servlets
- */
+
@ManagedAttribute(value="servlets", readonly=true)
public ServletHolder[] getServlets()
{
return _servlets;
}
- /* ------------------------------------------------------------ */
public ServletHolder getServlet(String name)
{
return _servletNameMap.get(name);
}
- /* ------------------------------------------------------------ */
@Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
@@ -526,16 +496,10 @@ public class ServletHandler extends ScopedHandler
}
}
- /* ------------------------------------------------------------ */
- /*
- * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
- */
@Override
public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
- DispatcherType type = baseRequest.getDispatcherType();
-
ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
FilterChain chain=null;
@@ -559,7 +523,6 @@ public class ServletHandler extends ScopedHandler
if (LOG.isDebugEnabled())
LOG.debug("chain={}",chain);
- Throwable th=null;
try
{
if (servlet_holder==null)
@@ -583,116 +546,13 @@ public class ServletHandler extends ScopedHandler
servlet_holder.handle(baseRequest,req,res);
}
}
- catch(EofException e)
- {
- throw e;
- }
- catch(RuntimeIOException e)
- {
- if (e.getCause() instanceof IOException)
- {
- LOG.debug(e);
- throw (IOException)e.getCause();
- }
- throw e;
- }
- catch(Exception e)
- {
- //TODO, can we let all error handling fall through to HttpChannel?
-
- if (baseRequest.isAsyncStarted() || !(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
- {
- if (e instanceof IOException)
- throw (IOException)e;
- if (e instanceof RuntimeException)
- throw (RuntimeException)e;
- if (e instanceof ServletException)
- throw (ServletException)e;
- }
-
- // unwrap cause
- th=e;
- if (th instanceof ServletException)
- {
- if (th instanceof QuietServletException)
- {
- LOG.warn(th.toString());
- LOG.debug(th);
- }
- else
- LOG.warn(th);
- }
- else if (th instanceof EofException)
- {
- throw (EofException)th;
- }
- else
- {
- LOG.warn(request.getRequestURI(),th);
- if (LOG.isDebugEnabled())
- LOG.debug(request.toString());
- }
-
- request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
- request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
- if (!response.isCommitted())
- {
- baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
- if (th instanceof UnavailableException)
- {
- UnavailableException ue = (UnavailableException)th;
- if (ue.isPermanent())
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
- else
- response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
- }
- else
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- else
- {
- if (th instanceof IOException)
- throw (IOException)th;
- if (th instanceof RuntimeException)
- throw (RuntimeException)th;
- if (th instanceof ServletException)
- throw (ServletException)th;
- throw new IllegalStateException("response already committed",th);
- }
- }
- catch(Error e)
- {
- if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
- throw e;
- th=e;
- if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
- throw e;
- LOG.warn("Error for "+request.getRequestURI(),e);
- if(LOG.isDebugEnabled())
- LOG.debug(request.toString());
-
- request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
- request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
- if (!response.isCommitted())
- {
- baseRequest.getResponse().getHttpFields().put(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- else
- LOG.debug("Response already committed for handling ",e);
- }
finally
{
- // Complete async errored requests
- if (th!=null && request.isAsyncStarted())
- baseRequest.getHttpChannelState().errorComplete();
-
if (servlet_holder!=null)
baseRequest.setHandled(true);
}
}
- /* ------------------------------------------------------------ */
protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
{
String key=pathInContext==null?servletHolder.getName():pathInContext;
@@ -700,7 +560,7 @@ public class ServletHandler extends ScopedHandler
if (_filterChainsCached && _chainCache!=null)
{
- FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
+ FilterChain chain = _chainCache[dispatch].get(key);
if (chain!=null)
return chain;
}
@@ -728,7 +588,7 @@ public class ServletHandler extends ScopedHandler
for (int i=0; i 0)
- chain= new CachedChain(filters, servletHolder);
+ chain = newCachedChain(filters, servletHolder);
final Map cache=_chainCache[dispatch];
final Queue lru=_chainLRU[dispatch];
@@ -904,7 +764,7 @@ public class ServletHandler extends ScopedHandler
/* ------------------------------------------------------------ */
/**
- * @return Returns the filterChainsCached.
+ * @return whether the filter chains are cached.
*/
public boolean isFilterChainsCached()
{
@@ -945,6 +805,15 @@ public class ServletHandler extends ScopedHandler
return new ListenerHolder(source);
}
+ /* ------------------------------------------------------------ */
+ /**
+ * Create a new CachedChain
+ */
+ public CachedChain newCachedChain(List filters, ServletHolder servletHolder)
+ {
+ return new CachedChain(filters, servletHolder);
+ }
+
/* ------------------------------------------------------------ */
/**
* Add a new servlet holder
@@ -1005,12 +874,10 @@ public class ServletHandler extends ScopedHandler
mapping.setPathSpec(pathSpec);
setServletMappings(ArrayUtil.addToArray(getServletMappings(), mapping, ServletMapping.class));
}
- catch (Exception e)
+ catch (RuntimeException e)
{
setServlets(holders);
- if (e instanceof RuntimeException)
- throw (RuntimeException)e;
- throw new RuntimeException(e);
+ throw e;
}
}
@@ -1113,17 +980,11 @@ public class ServletHandler extends ScopedHandler
addFilterMapping(mapping);
}
- catch (RuntimeException e)
+ catch (Throwable e)
{
setFilters(holders);
throw e;
}
- catch (Error e)
- {
- setFilters(holders);
- throw e;
- }
-
}
/* ------------------------------------------------------------ */
@@ -1180,12 +1041,7 @@ public class ServletHandler extends ScopedHandler
mapping.setDispatches(dispatches);
addFilterMapping(mapping);
}
- catch (RuntimeException e)
- {
- setFilters(holders);
- throw e;
- }
- catch (Error e)
+ catch (Throwable e)
{
setFilters(holders);
throw e;
@@ -1196,6 +1052,7 @@ public class ServletHandler extends ScopedHandler
/* ------------------------------------------------------------ */
/**
* Convenience method to add a filter with a mapping
+ *
* @param className the filter class name
* @param pathSpec the path spec
* @param dispatches the dispatcher types for this filter
@@ -1225,6 +1082,7 @@ public class ServletHandler extends ScopedHandler
/* ------------------------------------------------------------ */
/**
* Convenience method to add a preconstructed FilterHolder
+ *
* @param filter the filter holder
*/
public void addFilter (FilterHolder filter)
@@ -1236,6 +1094,7 @@ public class ServletHandler extends ScopedHandler
/* ------------------------------------------------------------ */
/**
* Convenience method to add a preconstructed FilterMapping
+ *
* @param mapping the filter mapping
*/
public void addFilterMapping (FilterMapping mapping)
@@ -1287,8 +1146,7 @@ public class ServletHandler extends ScopedHandler
{
if (mapping != null)
{
- Source source = mapping.getFilterHolder().getSource();
-
+ Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
FilterMapping[] mappings = getFilterMappings();
if (mappings==null || mappings.length==0)
{
@@ -1420,7 +1278,7 @@ public class ServletHandler extends ScopedHandler
else
{
_filterPathMappings=new ArrayList<>();
- _filterNameMappings=new MultiMap();
+ _filterNameMappings=new MultiMap<>();
for (FilterMapping filtermapping : _filterMappings)
{
FilterHolder filter_holder = _filterNameMap.get(filtermapping.getFilterName());
@@ -1450,10 +1308,10 @@ public class ServletHandler extends ScopedHandler
else
{
PathMap pm = new PathMap<>();
- Map servletPathMappings = new HashMap();
-
+ Map servletPathMappings = new HashMap<>();
+
//create a map of paths to set of ServletMappings that define that mapping
- HashMap> sms = new HashMap>();
+ HashMap> sms = new HashMap<>();
for (ServletMapping servletMapping : _servletMappings)
{
String[] pathSpecs = servletMapping.getPathSpecs();
@@ -1464,7 +1322,7 @@ public class ServletHandler extends ScopedHandler
Set mappings = sms.get(pathSpec);
if (mappings == null)
{
- mappings = new HashSet();
+ mappings = new HashSet<>();
sms.put(pathSpec, mappings);
}
mappings.add(servletMapping);
@@ -1490,7 +1348,7 @@ public class ServletHandler extends ScopedHandler
if (!servlet_holder.isEnabled())
continue;
- //only accept a default mapping if we don't have any other
+ //only accept a default mapping if we don't have any other
if (finalMapping == null)
finalMapping = mapping;
else
@@ -1621,7 +1479,7 @@ public class ServletHandler extends ScopedHandler
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
- private class CachedChain implements FilterChain
+ protected class CachedChain implements FilterChain
{
FilterHolder _filterHolder;
CachedChain _next;
@@ -1632,7 +1490,7 @@ public class ServletHandler extends ScopedHandler
* @param filters list of {@link FilterHolder} objects
* @param servletHolder
*/
- CachedChain(List filters, ServletHolder servletHolder)
+ protected CachedChain(List filters, ServletHolder servletHolder)
{
if (filters.size()>0)
{
@@ -1708,7 +1566,7 @@ public class ServletHandler extends ScopedHandler
int _filter= 0;
/* ------------------------------------------------------------ */
- Chain(Request baseRequest, List filters, ServletHolder servletHolder)
+ private Chain(Request baseRequest, List filters, ServletHolder servletHolder)
{
_baseRequest=baseRequest;
_chain= filters;
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 49df3a18173..0fed318d8b8 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
@@ -56,7 +56,7 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
-/**
+/**
* Servlet Instance and Context Holder.
*
* Holds the name, params and some state of a javax.servlet.Servlet
@@ -72,6 +72,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
private static final Logger LOG = Log.getLogger(ServletHolder.class);
private int _initOrder = -1;
private boolean _initOnStartup=false;
+ private boolean _initialized = false;
private Map _roleMap;
private String _forcedPath;
private String _runAsRole;
@@ -80,18 +81,17 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
private ServletRegistration.Dynamic _registration;
private JspContainer _jspContainer;
-
private transient Servlet _servlet;
private transient Config _config;
private transient long _unavailable;
private transient boolean _enabled = true;
private transient UnavailableException _unavailableEx;
-
+
public static final String APACHE_SENTINEL_CLASS = "org.apache.tomcat.InstanceManager";
public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix";
public static final Map NO_MAPPED_ROLES = Collections.emptyMap();
- public static enum JspContainer {APACHE, OTHER};
+ public static enum JspContainer {APACHE, OTHER};
/* ---------------------------------------------------------------- */
/** Constructor .
@@ -184,7 +184,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Set the initialize order.
*
* Holders with order<0, are initialized on use. Those with
@@ -199,7 +199,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Comparator by init order.
*/
@Override
@@ -288,7 +288,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
_enabled = enabled;
}
-
+
/* ------------------------------------------------------------ */
public void doStart()
throws Exception
@@ -296,7 +296,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
_unavailable=0;
if (!_enabled)
return;
-
+
// Handle JSP file forced paths
if (_forcedPath != null)
{
@@ -313,7 +313,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
setClassName(jsp.getClassName());
}
else
- {
+ {
if (getClassName() == null)
{
// Look for normal JSP servlet
@@ -334,12 +334,12 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
//container does not support startup precompilation, it will be compiled at runtime when handling a request for this jsp.
//See also adaptForcedPathToJspContainer
setInitParameter("jspFile", _forcedPath);
- }
+ }
}
}
}
-
-
+
+
//check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
try
{
@@ -386,33 +386,36 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
_servlet = new SingleThreadedWrapper();
-
+
}
-
-
+
+
/* ------------------------------------------------------------ */
@Override
public void initialize ()
throws Exception
{
- super.initialize();
- if (_extInstance || _initOnStartup)
- {
- try
+ if(!_initialized){
+ super.initialize();
+ if (_extInstance || _initOnStartup)
{
- initServlet();
- }
- catch(Exception e)
- {
- if (_servletHandler.isStartWithUnavailable())
- LOG.ignore(e);
- else
- throw e;
+ try
+ {
+ initServlet();
+ }
+ catch(Exception e)
+ {
+ if (_servletHandler.isStartWithUnavailable())
+ LOG.ignore(e);
+ else
+ throw e;
+ }
}
}
+ _initialized = true;
}
-
-
+
+
/* ------------------------------------------------------------ */
public void doStop()
throws Exception
@@ -442,6 +445,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
_servlet=null;
_config=null;
+ _initialized = false;
}
/* ------------------------------------------------------------ */
@@ -520,22 +524,22 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
return isStarted()&& _unavailable==0;
}
-
+
/* ------------------------------------------------------------ */
/**
* Check if there is a javax.servlet.annotation.ServletSecurity
* annotation on the servlet class. If there is, then we force
- * it to be loaded on startup, because all of the security
+ * it to be loaded on startup, because all of the security
* constraints must be calculated as the container starts.
- *
+ *
*/
private void checkInitOnStartup()
{
if (_class==null)
return;
-
+
if ((_class.getAnnotation(javax.servlet.annotation.ServletSecurity.class) != null) && !_initOnStartup)
- setInitOrder(Integer.MAX_VALUE);
+ setInitOrder(Integer.MAX_VALUE);
}
/* ------------------------------------------------------------ */
@@ -593,8 +597,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
_servlet=newInstance();
if (_config==null)
_config=new Config();
-
-
// Handle run as
if (_identityService!=null)
@@ -608,14 +610,11 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
initJspServlet();
detectJspContainer();
}
+ else if (_forcedPath != null)
+ detectJspContainer();
initMultiPart();
- if (_forcedPath != null && _jspContainer == null)
- {
- detectJspContainer();
- }
-
if (LOG.isDebugEnabled())
LOG.debug("Servlet.init {} for {}",_servlet,getName());
_servlet.init(_config);
@@ -657,10 +656,12 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
protected void initJspServlet () throws Exception
{
ContextHandler ch = ContextHandler.getContextHandler(getServletHandler().getServletContext());
-
+
/* Set the webapp's classpath for Jasper */
ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
+ System.err.println("JSP ("+ch+","+getName()+") CP="+ch.getClassPath());
+
/* Set up other classpath attribute */
if ("?".equals(getInitParameter("classpath")))
{
@@ -670,7 +671,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
if (classpath != null)
setInitParameter("classpath", classpath);
}
-
+
/* ensure scratch dir */
File scratch = null;
if (getInitParameter("scratchdir") == null)
@@ -679,7 +680,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
scratch = new File(tmp, "jsp");
setInitParameter("scratchdir", scratch.getAbsolutePath());
}
-
+
scratch = new File (getInitParameter("scratchdir"));
if (!scratch.exists()) scratch.mkdir();
}
@@ -688,7 +689,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
/**
* Register a ServletRequestListener that will ensure tmp multipart
* files are deleted when the request goes out of scope.
- *
+ *
* @throws Exception if unable to init the multipart
*/
protected void initMultiPart () throws Exception
@@ -737,11 +738,11 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
{
_runAsRole = role;
}
-
+
/* ------------------------------------------------------------ */
/**
* Prepare to service a request.
- *
+ *
* @param baseRequest the base request
* @param request the request
* @param response the response
@@ -756,7 +757,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
if (mpce != null)
baseRequest.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
}
-
+
public synchronized Servlet ensureInstance()
throws ServletException, UnavailableException
{
@@ -768,15 +769,15 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
if (_unavailable!=0 || (!_initOnStartup && servlet==null))
servlet=getServlet();
if (servlet==null)
- throw new UnavailableException("Could not instantiate "+_class);
-
+ throw new UnavailableException("Could not instantiate "+_class);
+
return servlet;
}
/* ------------------------------------------------------------ */
- /**
+ /**
* Service a request with this servlet.
- *
+ *
* @param baseRequest the base request
* @param request the request
* @param response the response
@@ -797,7 +798,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
Servlet servlet = ensureInstance();
// Service the request
- boolean servlet_error=true;
Object old_run_as = null;
boolean suspendable = baseRequest.isAsyncSupported();
try
@@ -814,7 +814,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
baseRequest.setAsyncSupported(false);
servlet.service(request,response);
- servlet_error=false;
}
catch(UnavailableException e)
{
@@ -825,13 +824,9 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
{
baseRequest.setAsyncSupported(suspendable);
- // pop run-as role
+ // Pop run-as role.
if (_identityService!=null)
_identityService.unsetRunAs(old_run_as);
-
- // Handle error params.
- if (servlet_error)
- request.setAttribute("javax.servlet.error.servlet_name",getName());
}
}
@@ -877,7 +872,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
try
{
//check for apache
- Loader.loadClass(Holder.class, APACHE_SENTINEL_CLASS);
+ Loader.loadClass(APACHE_SENTINEL_CLASS);
if (LOG.isDebugEnabled())LOG.debug("Apache jasper detected");
_jspContainer = JspContainer.APACHE;
}
@@ -894,12 +889,12 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
{
if (jsp == null)
return "";
-
+
int i = jsp.lastIndexOf('/') + 1;
jsp = jsp.substring(i);
try
{
- Class> jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+ Class> jspUtil = Loader.loadClass("org.apache.jasper.compiler.JspUtil");
Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class);
return (String)makeJavaIdentifier.invoke(null, jsp);
}
@@ -912,23 +907,23 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
return tmp;
}
}
-
-
+
+
/* ------------------------------------------------------------ */
private String getPackageOfJspClass (String jsp)
{
if (jsp == null)
return "";
-
+
int i = jsp.lastIndexOf('/');
if (i <= 0)
return "";
try
{
- Class> jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil");
+ 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));
- }
+ }
catch (Exception e)
{
String tmp = jsp.substring(1).replace('/','.');
@@ -938,25 +933,25 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
return tmp;
}
}
-
-
+
+
/* ------------------------------------------------------------ */
private String getJspPackagePrefix ()
{
String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME );
if (jspPackageName == null)
jspPackageName = "org.apache.jsp";
-
+
return jspPackageName;
}
-
-
+
+
/* ------------------------------------------------------------ */
private String getClassNameForJsp (String jsp)
{
if (jsp == null)
- return null;
-
+ return null;
+
return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp);
}
@@ -1203,7 +1198,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope
throw se;
}
}
-
+
/* ------------------------------------------------------------ */
@Override
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
index ab181862f46..ae10b7d8729 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletMapping.java
@@ -65,6 +65,25 @@ public class ServletMapping
{
_pathSpecs = pathSpecs;
}
+
+
+ /* ------------------------------------------------------------ */
+ /** Test if the list of path specs contains a particular one.
+ * @param pathSpec the path spec
+ * @return true if path spec matches something in mappings
+ */
+ public boolean containsPathSpec (String pathSpec)
+ {
+ if (_pathSpecs == null || _pathSpecs.length == 0)
+ return false;
+
+ for (String p:_pathSpecs)
+ {
+ if (p.equals(pathSpec))
+ return true;
+ }
+ return false;
+ }
/* ------------------------------------------------------------ */
/**
diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
index ed4987138f3..40303b1c26e 100644
--- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
+++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
@@ -52,7 +52,7 @@ public class ELContextCleaner implements ServletContextListener
try
{
//Check that the BeanELResolver class is on the classpath
- Class> beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
+ Class> beanELResolver = Loader.loadClass("javax.el.BeanELResolver");
//Get a reference via reflection to the properties field which is holding class references
Field field = getField(beanELResolver);
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
index c8ce6638f7f..41b567589dc 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java
@@ -18,14 +18,10 @@
package org.eclipse.jetty.servlet;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
+import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
@@ -38,7 +34,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
-import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
@@ -52,14 +47,20 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
/**
* This tests the correct functioning of the AsyncContext
- *
+ *
* tests for #371649 and #371635
*/
public class AsyncContextTest
{
-
private Server _server;
private ServletContextHandler _contextHandler;
private LocalConnector _connector;
@@ -68,32 +69,31 @@ public class AsyncContextTest
public void setUp() throws Exception
{
_server = new Server();
- _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
_connector = new LocalConnector(_server);
_connector.setIdleTimeout(5000);
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
- _server.setConnectors(new Connector[]
- { _connector });
+ _server.addConnector(_connector);
+ _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
_contextHandler.setContextPath("/ctx");
- _contextHandler.addServlet(new ServletHolder(new TestServlet()),"/servletPath");
- _contextHandler.addServlet(new ServletHolder(new TestServlet()),"/path with spaces/servletPath");
- _contextHandler.addServlet(new ServletHolder(new TestServlet2()),"/servletPath2");
- _contextHandler.addServlet(new ServletHolder(new TestStartThrowServlet()),"/startthrow/*");
- _contextHandler.addServlet(new ServletHolder(new ForwardingServlet()),"/forward");
- _contextHandler.addServlet(new ServletHolder(new AsyncDispatchingServlet()),"/dispatchingServlet");
- _contextHandler.addServlet(new ServletHolder(new ExpireServlet()),"/expire/*");
- _contextHandler.addServlet(new ServletHolder(new BadExpireServlet()),"/badexpire/*");
- _contextHandler.addServlet(new ServletHolder(new ErrorServlet()),"/error/*");
-
+ _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/servletPath");
+ _contextHandler.addServlet(new ServletHolder(new TestServlet()), "/path with spaces/servletPath");
+ _contextHandler.addServlet(new ServletHolder(new TestServlet2()), "/servletPath2");
+ _contextHandler.addServlet(new ServletHolder(new TestStartThrowServlet()), "/startthrow/*");
+ _contextHandler.addServlet(new ServletHolder(new ForwardingServlet()), "/forward");
+ _contextHandler.addServlet(new ServletHolder(new AsyncDispatchingServlet()), "/dispatchingServlet");
+ _contextHandler.addServlet(new ServletHolder(new ExpireServlet()), "/expire/*");
+ _contextHandler.addServlet(new ServletHolder(new BadExpireServlet()), "/badexpire/*");
+ _contextHandler.addServlet(new ServletHolder(new ErrorServlet()), "/error/*");
+
ErrorPageErrorHandler error_handler = new ErrorPageErrorHandler();
_contextHandler.setErrorHandler(error_handler);
- error_handler.addErrorPage(500,"/error/500");
- error_handler.addErrorPage(IOException.class.getName(),"/error/IOE");
+ error_handler.addErrorPage(500, "/error/500");
+ error_handler.addErrorPage(IOException.class.getName(), "/error/IOE");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[]
- { _contextHandler, new DefaultHandler() });
+ {_contextHandler, new DefaultHandler()});
_server.setHandler(handlers);
_server.start();
@@ -108,103 +108,92 @@ public class AsyncContextTest
@Test
public void testSimpleAsyncContext() throws Exception
{
- String request = "GET /ctx/servletPath HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n"
- + "Connection: close\r\n" + "\r\n";
+ String request =
+ "GET /ctx/servletPath HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
-
- BufferedReader br = parseHeader(responseString);
-
- Assert.assertEquals("servlet gets right path", "doGet:getServletPath:/servletPath", br.readLine());
- Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath",br.readLine());
- Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath",br.readLine());
-
+ assertThat(responseString, startsWith("HTTP/1.1 200 "));
+ assertThat(responseString, containsString("doGet:getServletPath:/servletPath"));
+ assertThat(responseString, containsString("doGet:async:getServletPath:/servletPath"));
+ assertThat(responseString, containsString("async:run:attr:servletPath:/servletPath"));
}
@Test
public void testStartThrow() throws Exception
{
- String request =
- "GET /ctx/startthrow HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Connection: close\r\n" +
- "\r\n";
- String responseString = _connector.getResponses(request);
+ String request =
+ "GET /ctx/startthrow HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
+ String responseString = _connector.getResponses(request,10,TimeUnit.MINUTES);
- BufferedReader br = new BufferedReader(new StringReader(responseString));
-
- assertEquals("HTTP/1.1 500 Server Error",br.readLine());
- br.readLine();// connection close
- br.readLine();// server
- br.readLine();// empty
-
- Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
- Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
+ assertThat(responseString, startsWith("HTTP/1.1 500 "));
+ assertThat(responseString, containsString("ERROR: /error"));
+ assertThat(responseString, containsString("PathInfo= /IOE"));
+ assertThat(responseString, containsString("EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test"));
}
@Test
public void testStartDispatchThrow() throws Exception
{
- String request = "GET /ctx/startthrow?dispatch=true HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Content-Type: application/x-www-form-urlencoded\r\n" +
- "Connection: close\r\n" +
- "\r\n";
+ String request = "" +
+ "GET /ctx/startthrow?dispatch=true HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
- BufferedReader br = new BufferedReader(new StringReader(responseString));
-
- assertEquals("HTTP/1.1 500 Server Error",br.readLine());
- br.readLine();// connection close
- br.readLine();// server
- br.readLine();// empty
- Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
- Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
+ assertThat(responseString, startsWith("HTTP/1.1 500 "));
+ assertThat(responseString, containsString("ERROR: /error"));
+ assertThat(responseString, containsString("PathInfo= /IOE"));
+ assertThat(responseString, containsString("EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test"));
}
-
+
@Test
public void testStartCompleteThrow() throws Exception
{
- String request = "GET /ctx/startthrow?complete=true HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Content-Type: application/x-www-form-urlencoded\r\n" +
- "Connection: close\r\n" +
- "\r\n";
+ String request = "GET /ctx/startthrow?complete=true HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
BufferedReader br = new BufferedReader(new StringReader(responseString));
- assertEquals("HTTP/1.1 500 Server Error",br.readLine());
+ assertEquals("HTTP/1.1 500 Server Error", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
- Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
- Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test",br.readLine());
+ Assert.assertEquals("ERROR: /error", br.readLine());
+ Assert.assertEquals("PathInfo= /IOE", br.readLine());
+ Assert.assertEquals("EXCEPTION: org.eclipse.jetty.server.QuietServletException: java.io.IOException: Test", br.readLine());
}
-
+
@Test
public void testStartFlushCompleteThrow() throws Exception
{
- String request = "GET /ctx/startthrow?flush=true&complete=true HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Content-Type: application/x-www-form-urlencoded\r\n" +
- "Connection: close\r\n" +
- "\r\n";
+ String request = "GET /ctx/startthrow?flush=true&complete=true HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
BufferedReader br = new BufferedReader(new StringReader(responseString));
- assertEquals("HTTP/1.1 200 OK",br.readLine());
+ assertEquals("HTTP/1.1 200 OK", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
- Assert.assertEquals("error servlet","completeBeforeThrow",br.readLine());
+ Assert.assertEquals("error servlet", "completeBeforeThrow", br.readLine());
}
-
+
@Test
public void testDispatchAsyncContext() throws Exception
{
@@ -214,13 +203,13 @@ public class AsyncContextTest
BufferedReader br = parseHeader(responseString);
- Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath2",br.readLine());
- Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath2",br.readLine());
- Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath",br.readLine());
- Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null",br.readLine());
- Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
- Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/ctx",br.readLine());
- Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/ctx/servletPath",br.readLine());
+ Assert.assertEquals("servlet gets right path", "doGet:getServletPath:/servletPath2", br.readLine());
+ Assert.assertEquals("async context gets right path in get", "doGet:async:getServletPath:/servletPath2", br.readLine());
+ Assert.assertEquals("servlet path attr is original", "async:run:attr:servletPath:/servletPath", br.readLine());
+ Assert.assertEquals("path info attr is correct", "async:run:attr:pathInfo:null", br.readLine());
+ Assert.assertEquals("query string attr is correct", "async:run:attr:queryString:dispatch=true", br.readLine());
+ Assert.assertEquals("context path attr is correct", "async:run:attr:contextPath:/ctx", br.readLine());
+ Assert.assertEquals("request uri attr is correct", "async:run:attr:requestURI:/ctx/servletPath", br.readLine());
try
{
@@ -229,7 +218,7 @@ public class AsyncContextTest
}
catch (IllegalStateException e)
{
-
+
}
}
@@ -242,13 +231,13 @@ public class AsyncContextTest
BufferedReader br = parseHeader(responseString);
- assertThat("servlet gets right path",br.readLine(),equalTo("doGet:getServletPath:/servletPath2"));
- assertThat("async context gets right path in get",br.readLine(), equalTo("doGet:async:getServletPath:/servletPath2"));
- assertThat("servlet path attr is original",br.readLine(),equalTo("async:run:attr:servletPath:/path with spaces/servletPath"));
- assertThat("path info attr is correct",br.readLine(),equalTo("async:run:attr:pathInfo:null"));
- assertThat("query string attr is correct",br.readLine(),equalTo("async:run:attr:queryString:dispatch=true&queryStringWithEncoding=space%20space"));
- assertThat("context path attr is correct",br.readLine(),equalTo("async:run:attr:contextPath:/ctx"));
- assertThat("request uri attr is correct",br.readLine(),equalTo("async:run:attr:requestURI:/ctx/path%20with%20spaces/servletPath"));
+ assertThat("servlet gets right path", br.readLine(), equalTo("doGet:getServletPath:/servletPath2"));
+ assertThat("async context gets right path in get", br.readLine(), equalTo("doGet:async:getServletPath:/servletPath2"));
+ assertThat("servlet path attr is original", br.readLine(), equalTo("async:run:attr:servletPath:/path with spaces/servletPath"));
+ assertThat("path info attr is correct", br.readLine(), equalTo("async:run:attr:pathInfo:null"));
+ assertThat("query string attr is correct", br.readLine(), equalTo("async:run:attr:queryString:dispatch=true&queryStringWithEncoding=space%20space"));
+ assertThat("context path attr is correct", br.readLine(), equalTo("async:run:attr:contextPath:/ctx"));
+ assertThat("request uri attr is correct", br.readLine(), equalTo("async:run:attr:requestURI:/ctx/path%20with%20spaces/servletPath"));
}
@Test
@@ -261,9 +250,9 @@ public class AsyncContextTest
BufferedReader br = parseHeader(responseString);
- Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath",br.readLine());
- Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath",br.readLine());
- Assert.assertEquals("async context gets right path in async","async:run:attr:servletPath:/servletPath",br.readLine());
+ Assert.assertEquals("servlet gets right path", "doGet:getServletPath:/servletPath", br.readLine());
+ Assert.assertEquals("async context gets right path in get", "doGet:async:getServletPath:/servletPath", br.readLine());
+ Assert.assertEquals("async context gets right path in async", "async:run:attr:servletPath:/servletPath", br.readLine());
}
@Test
@@ -276,13 +265,13 @@ public class AsyncContextTest
BufferedReader br = parseHeader(responseString);
- Assert.assertEquals("servlet gets right path","doGet:getServletPath:/servletPath2",br.readLine());
- Assert.assertEquals("async context gets right path in get","doGet:async:getServletPath:/servletPath2",br.readLine());
- Assert.assertEquals("servlet path attr is original","async:run:attr:servletPath:/servletPath",br.readLine());
- Assert.assertEquals("path info attr is correct","async:run:attr:pathInfo:null",br.readLine());
- Assert.assertEquals("query string attr is correct","async:run:attr:queryString:dispatch=true",br.readLine());
- Assert.assertEquals("context path attr is correct","async:run:attr:contextPath:/ctx",br.readLine());
- Assert.assertEquals("request uri attr is correct","async:run:attr:requestURI:/ctx/servletPath",br.readLine());
+ Assert.assertEquals("servlet gets right path", "doGet:getServletPath:/servletPath2", br.readLine());
+ Assert.assertEquals("async context gets right path in get", "doGet:async:getServletPath:/servletPath2", br.readLine());
+ Assert.assertEquals("servlet path attr is original", "async:run:attr:servletPath:/servletPath", br.readLine());
+ Assert.assertEquals("path info attr is correct", "async:run:attr:pathInfo:null", br.readLine());
+ Assert.assertEquals("query string attr is correct", "async:run:attr:queryString:dispatch=true", br.readLine());
+ Assert.assertEquals("context path attr is correct", "async:run:attr:contextPath:/ctx", br.readLine());
+ Assert.assertEquals("request uri attr is correct", "async:run:attr:requestURI:/ctx/servletPath", br.readLine());
}
@Test
@@ -293,30 +282,30 @@ public class AsyncContextTest
String responseString = _connector.getResponses(request);
BufferedReader br = parseHeader(responseString);
- assertThat("!ForwardingServlet",br.readLine(),equalTo("Dispatched back to ForwardingServlet"));
+ assertThat("!ForwardingServlet", br.readLine(), equalTo("Dispatched back to ForwardingServlet"));
}
@Test
public void testDispatchRequestResponse() throws Exception
{
- String request = "GET /ctx/forward?dispatchRequestResponse=true HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Content-Type: application/x-www-form-urlencoded\r\n" +
- "Connection: close\r\n" +
- "\r\n";
+ String request = "GET /ctx/forward?dispatchRequestResponse=true HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
BufferedReader br = parseHeader(responseString);
- assertThat("!AsyncDispatchingServlet",br.readLine(),equalTo("Dispatched back to AsyncDispatchingServlet"));
+ assertThat("!AsyncDispatchingServlet", br.readLine(), equalTo("Dispatched back to AsyncDispatchingServlet"));
}
private BufferedReader parseHeader(String responseString) throws IOException
{
BufferedReader br = new BufferedReader(new StringReader(responseString));
- assertEquals("HTTP/1.1 200 OK",br.readLine());
+ assertEquals("HTTP/1.1 200 OK", br.readLine());
br.readLine();// connection close
br.readLine();// server
@@ -337,17 +326,17 @@ public class AsyncContextTest
}
else
{
- request.getRequestDispatcher("/dispatchingServlet").forward(request,response);
+ request.getRequestDispatcher("/dispatchingServlet").forward(request, response);
}
}
}
- public static volatile AsyncContext __asyncContext;
-
+ public static volatile AsyncContext __asyncContext;
+
private class AsyncDispatchingServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
-
+
@Override
protected void doGet(HttpServletRequest req, final HttpServletResponse response) throws ServletException, IOException
{
@@ -364,12 +353,12 @@ public class AsyncContextTest
{
wrapped = true;
asyncContext = request.startAsync(request, new Wrapper(response));
- __asyncContext=asyncContext;
+ __asyncContext = asyncContext;
}
else
{
asyncContext = request.startAsync();
- __asyncContext=asyncContext;
+ __asyncContext = asyncContext;
}
new Thread(new DispatchingRunnable(asyncContext, wrapped)).start();
@@ -380,44 +369,44 @@ public class AsyncContextTest
@Test
public void testExpire() throws Exception
{
- String request = "GET /ctx/expire HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
+ String request = "GET /ctx/expire HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
- "Connection: close\r\n" +
+ "Connection: close\r\n" +
"\r\n";
String responseString = _connector.getResponses(request);
-
+
BufferedReader br = new BufferedReader(new StringReader(responseString));
- assertEquals("HTTP/1.1 500 Async Timeout",br.readLine());
+ assertEquals("HTTP/1.1 500 Server Error", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
- Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
+ Assert.assertEquals("error servlet", "ERROR: /error", br.readLine());
}
@Test
public void testBadExpire() throws Exception
{
- String request = "GET /ctx/badexpire HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Content-Type: application/x-www-form-urlencoded\r\n" +
- "Connection: close\r\n" +
- "\r\n";
+ String request = "GET /ctx/badexpire HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Content-Type: application/x-www-form-urlencoded\r\n" +
+ "Connection: close\r\n" +
+ "\r\n";
String responseString = _connector.getResponses(request);
-
+
BufferedReader br = new BufferedReader(new StringReader(responseString));
- assertEquals("HTTP/1.1 500 Server Error",br.readLine());
+ assertEquals("HTTP/1.1 500 Server Error", br.readLine());
br.readLine();// connection close
br.readLine();// server
br.readLine();// empty
- Assert.assertEquals("error servlet","ERROR: /error",br.readLine());
- Assert.assertEquals("error servlet","PathInfo= /500",br.readLine());
- Assert.assertEquals("error servlet","EXCEPTION: java.lang.RuntimeException: TEST",br.readLine());
+ Assert.assertEquals("error servlet", "ERROR: /error", br.readLine());
+ Assert.assertEquals("error servlet", "PathInfo= /500", br.readLine());
+ Assert.assertEquals("error servlet", "EXCEPTION: java.lang.RuntimeException: TEST", br.readLine());
}
private class DispatchingRunnable implements Runnable
@@ -456,11 +445,11 @@ public class AsyncContextTest
{
response.getOutputStream().print("ERROR: " + request.getServletPath() + "\n");
response.getOutputStream().print("PathInfo= " + request.getPathInfo() + "\n");
- if (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION)!=null)
+ if (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) != null)
response.getOutputStream().print("EXCEPTION: " + request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) + "\n");
}
}
-
+
private class ExpireServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@@ -468,14 +457,14 @@ public class AsyncContextTest
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- if (request.getDispatcherType()==DispatcherType.REQUEST)
+ if (request.getDispatcherType() == DispatcherType.REQUEST)
{
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(100);
}
}
}
-
+
private class BadExpireServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@@ -483,7 +472,7 @@ public class AsyncContextTest
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- if (request.getDispatcherType()==DispatcherType.REQUEST)
+ if (request.getDispatcherType() == DispatcherType.REQUEST)
{
AsyncContext asyncContext = request.startAsync();
asyncContext.addListener(new AsyncListener()
@@ -493,27 +482,27 @@ public class AsyncContextTest
{
throw new RuntimeException("TEST");
}
-
+
@Override
public void onStartAsync(AsyncEvent event) throws IOException
- {
+ {
}
-
+
@Override
public void onError(AsyncEvent event) throws IOException
- {
+ {
}
-
+
@Override
public void onComplete(AsyncEvent event) throws IOException
- {
+ {
}
});
asyncContext.setTimeout(100);
}
}
}
-
+
private class TestServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@@ -523,15 +512,15 @@ public class AsyncContextTest
{
if (request.getParameter("dispatch") != null)
{
- AsyncContext asyncContext = request.startAsync(request,response);
- __asyncContext=asyncContext;
+ AsyncContext asyncContext = request.startAsync(request, response);
+ __asyncContext = asyncContext;
asyncContext.dispatch("/servletPath2");
}
else
{
response.getOutputStream().print("doGet:getServletPath:" + request.getServletPath() + "\n");
- AsyncContext asyncContext = request.startAsync(request,response);
- __asyncContext=asyncContext;
+ AsyncContext asyncContext = request.startAsync(request, response);
+ __asyncContext = asyncContext;
response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");
asyncContext.start(new AsyncRunnable(asyncContext));
@@ -548,12 +537,12 @@ public class AsyncContextTest
{
response.getOutputStream().print("doGet:getServletPath:" + request.getServletPath() + "\n");
AsyncContext asyncContext = request.startAsync(request, response);
- __asyncContext=asyncContext;
+ __asyncContext = asyncContext;
response.getOutputStream().print("doGet:async:getServletPath:" + ((HttpServletRequest)asyncContext.getRequest()).getServletPath() + "\n");
asyncContext.start(new AsyncRunnable(asyncContext));
}
}
-
+
private class TestStartThrowServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
@@ -561,10 +550,10 @@ public class AsyncContextTest
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- if (request.getDispatcherType()==DispatcherType.REQUEST)
+ if (request.getDispatcherType() == DispatcherType.REQUEST)
{
request.startAsync(request, response);
-
+
if (Boolean.valueOf(request.getParameter("dispatch")))
{
request.getAsyncContext().dispatch();
@@ -577,7 +566,7 @@ public class AsyncContextTest
response.flushBuffer();
request.getAsyncContext().complete();
}
-
+
throw new QuietServletException(new IOException("Test"));
}
}
@@ -615,7 +604,7 @@ public class AsyncContextTest
private class Wrapper extends HttpServletResponseWrapper
{
- public Wrapper (HttpServletResponse response)
+ public Wrapper(HttpServletResponse response)
{
super(response);
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
index d1e0379b9c3..034a26c1f89 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java
@@ -87,7 +87,7 @@ public class AsyncIOServletTest
context.addEventListener(new ContextHandler.ContextScopeListener()
{
@Override
- public void enterScope(Context context, Request request)
+ public void enterScope(Context context, Request request, Object reason)
{
if (scope.get()!=null)
throw new IllegalStateException();
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java
index dd7d6303b47..fb5493a71b6 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncListenerTest.java
@@ -18,631 +18,406 @@
package org.eclipse.jetty.servlet;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.EnumSet;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
-import javax.servlet.DispatcherType;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
-import org.junit.Ignore;
+import org.junit.After;
import org.junit.Test;
-@Ignore("Not handling Exceptions during Async very well")
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertThat;
+
public class AsyncListenerTest
{
- // Unique named RuntimeException to help during debugging / assertions
- @SuppressWarnings("serial")
- public static class FooRuntimeException extends RuntimeException
+ private Server server;
+ private LocalConnector connector;
+
+ public void startServer(ServletContextHandler context) throws Exception
{
- }
-
- // Unique named Exception to help during debugging / assertions
- @SuppressWarnings("serial")
- public static class FooException extends Exception
- {
- }
-
- // Unique named Throwable to help during debugging / assertions
- @SuppressWarnings("serial")
- public static class FooThrowable extends Throwable
- {
- }
-
- // Unique named Error to help during debugging / assertions
- @SuppressWarnings("serial")
- public static class FooError extends Error
- {
- }
-
- /**
- * Basic AsyncListener adapter that simply logs (and makes testcase writing easier)
- */
- public static class AsyncListenerAdapter implements AsyncListener
- {
- private static final Logger LOG = Log.getLogger(AsyncListenerTest.AsyncListenerAdapter.class);
-
- @Override
- public void onComplete(AsyncEvent event) throws IOException
- {
- LOG.info("onComplete({})",event);
- }
-
- @Override
- public void onTimeout(AsyncEvent event) throws IOException
- {
- LOG.info("onTimeout({})",event);
- }
-
- @Override
- public void onError(AsyncEvent event) throws IOException
- {
- LOG.info("onError({})",event);
- }
-
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException
- {
- LOG.info("onStartAsync({})",event);
- }
- }
-
- /**
- * Common ErrorContext for normal and async error handling
- */
- public static class ErrorContext implements AsyncListener
- {
- private static final Logger LOG = Log.getLogger(AsyncListenerTest.ErrorContext.class);
-
- public void report(Throwable t, ServletRequest req, ServletResponse resp) throws IOException
- {
- if (resp instanceof HttpServletResponse)
- {
- ((HttpServletResponse)resp).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- resp.setContentType("text/plain");
- resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
- PrintWriter out = resp.getWriter();
- t.printStackTrace(out);
- }
-
- private void reportThrowable(AsyncEvent event) throws IOException
- {
- Throwable t = event.getThrowable();
- if (t == null)
- {
- return;
- }
- ServletRequest req = event.getAsyncContext().getRequest();
- ServletResponse resp = event.getAsyncContext().getResponse();
- report(t,req,resp);
- }
-
- @Override
- public void onComplete(AsyncEvent event) throws IOException
- {
- LOG.info("onComplete({})",event);
- reportThrowable(event);
- }
-
- @Override
- public void onTimeout(AsyncEvent event) throws IOException
- {
- LOG.info("onTimeout({})",event);
- reportThrowable(event);
- }
-
- @Override
- public void onError(AsyncEvent event) throws IOException
- {
- LOG.info("onError({})",event);
- reportThrowable(event);
- }
-
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException
- {
- LOG.info("onStartAsync({})",event);
- reportThrowable(event);
- }
- }
-
- /**
- * Common filter for all test cases that should handle Errors in a consistent way
- * regardless of how the exception / error occurred in the servlets in the chain.
- */
- public static class ErrorFilter implements Filter
- {
- private final List tracking;
-
- public ErrorFilter(List tracking)
- {
- this.tracking = tracking;
- }
-
- @Override
- public void destroy()
- {
- }
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
- {
- ErrorContext err = new ErrorContext();
- tracking.add(err);
- try
- {
- chain.doFilter(request,response);
- }
- catch (Throwable t)
- {
- err.report(t,request,response);
- }
- finally
- {
- if (request.isAsyncStarted())
- {
- request.getAsyncContext().addListener(err);
- }
- }
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException
- {
- }
- }
-
- /**
- * Normal non-async testcase of error handling from a filter
- *
- * @throws Exception
- * on test failure
- */
- @Test
- public void testFilterErrorNoAsync() throws Exception
- {
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
- {
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- throw new FooRuntimeException();
- }
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
+ server = new Server();
+ connector = new LocalConnector(server);
+ connector.setIdleTimeout(20 * 60 * 1000L);
+ server.addConnector(connector);
server.setHandler(context);
-
- try
- {
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
- {
- server.stop();
- }
+ server.start();
}
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, then application Exception
- *
- * @throws Exception
- * on test failure
- */
- @Test
- public void testFilterErrorAsyncStart_Exception() throws Exception
+ @After
+ public void dispose() throws Exception
{
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
- {
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- req.startAsync();
- // before listeners are added, toss Exception
- throw new FooRuntimeException();
- }
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
- server.setHandler(context);
-
- try
- {
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
- {
+ if (server != null)
server.stop();
- }
}
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, add listener that does nothing, then application Exception
- *
- * @throws Exception
- * on test failure
- */
@Test
- public void testFilterErrorAsyncStart_AddEmptyListener_Exception() throws Exception
+ public void test_StartAsync_Throw_OnError_Dispatch() throws Exception
{
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
- {
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- AsyncContext ctx = req.startAsync();
- ctx.addListener(new AsyncListenerAdapter());
- throw new FooRuntimeException();
- }
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
- server.setHandler(context);
-
- try
- {
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
- {
- server.stop();
- }
+ test_StartAsync_Throw_OnError(event -> event.getAsyncContext().dispatch("/dispatch"));
+ String httpResponse = connector.getResponses("" +
+ "GET /ctx/path HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 200 "));
}
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, add listener that completes only, then application Exception
- *
- * @throws Exception
- * on test failure
- */
@Test
- public void testFilterErrorAsyncStart_AddListener_Exception() throws Exception
+ public void test_StartAsync_Throw_OnError_Complete() throws Exception
{
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
+ test_StartAsync_Throw_OnError(event ->
{
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ ServletOutputStream output = response.getOutputStream();
+ output.println(event.getThrowable().getClass().getName());
+ output.println("COMPLETE");
+ event.getAsyncContext().complete();
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET /ctx/path HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 500 "));
+ assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ assertThat(httpResponse, containsString("COMPLETE"));
+ }
+
+ @Test
+ public void test_StartAsync_Throw_OnError_Throw() throws Exception
+ {
+ test_StartAsync_Throw_OnError(event ->
+ {
+ throw new IOException();
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET /ctx/path HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 500 "));
+ assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ }
+
+ @Test
+ public void test_StartAsync_Throw_OnError_Nothing() throws Exception
+ {
+ test_StartAsync_Throw_OnError(event -> {});
+ String httpResponse = connector.getResponses("" +
+ "GET /ctx/path HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 500 "));
+ assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ }
+
+ @Test
+ public void test_StartAsync_Throw_OnError_SendError() throws Exception
+ {
+ test_StartAsync_Throw_OnError(event ->
+ {
+ HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
+ response.sendError(HttpStatus.BAD_GATEWAY_502);
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET /ctx/path HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 502 "));
+ assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ }
+
+ @Test
+ public void test_StartAsync_Throw_OnError_SendError_CustomErrorPage() throws Exception
+ {
+ test_StartAsync_Throw_OnError(event ->
+ {
+ HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
+ response.sendError(HttpStatus.BAD_GATEWAY_502);
+ });
+
+ // Add a custom error page.
+ ErrorPageErrorHandler errorHandler = new ErrorPageErrorHandler();
+ errorHandler.setServer(server);
+ errorHandler.addErrorPage(HttpStatus.BAD_GATEWAY_502, "/error");
+ server.addManaged(errorHandler);
+
+ String httpResponse = connector.getResponses("" +
+ "GET /ctx/path HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n", 10, TimeUnit.MINUTES);
+ assertThat(httpResponse, containsString("HTTP/1.1 502 "));
+ assertThat(httpResponse, containsString("CUSTOM"));
+ }
+
+ private void test_StartAsync_Throw_OnError(IOConsumer consumer) throws Exception
+ {
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/ctx");
+ context.addServlet(new ServletHolder(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- AsyncContext ctx = req.startAsync();
- ctx.addListener(new AsyncListenerAdapter()
+ AsyncContext asyncContext = request.startAsync();
+ asyncContext.setTimeout(0);
+ asyncContext.addListener(new AsyncListenerAdapter()
{
@Override
public void onError(AsyncEvent event) throws IOException
{
- System.err.println("### ONERROR");
- event.getThrowable().printStackTrace(System.err);
- event.getAsyncContext().complete();
+ consumer.accept(event);
}
});
- throw new FooRuntimeException();
+ throw new TestRuntimeException();
}
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
- server.setHandler(context);
-
- try
+ }), "/path/*");
+ context.addServlet(new ServletHolder(new HttpServlet()
{
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.setStatus(HttpStatus.OK_200);
+ }
+ }), "/dispatch/*");
+ context.addServlet(new ServletHolder(new HttpServlet()
{
- server.stop();
- }
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.getOutputStream().print("CUSTOM");
+ }
+ }), "/error/*");
+
+ startServer(context);
}
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, add listener, in onStartAsync throw Exception
- *
- * @throws Exception
- * on test failure
- */
@Test
- public void testFilterErrorAsyncStart_AddListener_ExceptionDuringOnStart() throws Exception
+ public void test_StartAsync_OnTimeout_Dispatch() throws Exception
{
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
- {
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- AsyncContext ctx = req.startAsync();
- ctx.addListener(new AsyncListenerAdapter()
- {
- @Override
- public void onStartAsync(AsyncEvent event) throws IOException
- {
- throw new FooRuntimeException();
- }
- });
- }
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
- server.setHandler(context);
-
- try
- {
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
- {
- server.stop();
- }
- }
-
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, add listener, in onComplete throw Exception
- *
- * @throws Exception
- * on test failure
- */
- @Test
- public void testFilterErrorAsyncStart_AddListener_ExceptionDuringOnComplete() throws Exception
- {
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
- {
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
- {
- AsyncContext ctx = req.startAsync();
- ctx.addListener(new AsyncListenerAdapter()
- {
- @Override
- public void onComplete(AsyncEvent event) throws IOException
- {
- throw new FooRuntimeException();
- }
- });
- ctx.complete();
- }
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
- server.setHandler(context);
-
- try
- {
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
- {
- server.stop();
- }
+ test_StartAsync_OnTimeout(500, event -> event.getAsyncContext().dispatch("/dispatch"));
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 200 "));
}
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, add listener, in onTimeout throw Exception
- *
- * @throws Exception
- * on test failure
- */
@Test
- public void testFilterErrorAsyncStart_AddListener_ExceptionDuringOnTimeout() throws Exception
+ public void test_StartAsync_OnTimeout_Complete() throws Exception
{
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
- ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
+ test_StartAsync_OnTimeout(500, event ->
{
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
+ response.setStatus(HttpStatus.OK_200);
+ ServletOutputStream output = response.getOutputStream();
+ output.println("COMPLETE");
+ event.getAsyncContext().complete();
+
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 200 "));
+ assertThat(httpResponse, containsString("COMPLETE"));
+ }
+
+ @Test
+ public void test_StartAsync_OnTimeout_Throw() throws Exception
+ {
+ test_StartAsync_OnTimeout(500, event ->
+ {
+ throw new TestRuntimeException();
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 500 "));
+ assertThat(httpResponse, containsString(TestRuntimeException.class.getName()));
+ }
+
+ @Test
+ public void test_StartAsync_OnTimeout_Nothing() throws Exception
+ {
+ test_StartAsync_OnTimeout(500, event -> {
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 500 "));
+ }
+
+ @Test
+ public void test_StartAsync_OnTimeout_SendError() throws Exception
+ {
+ test_StartAsync_OnTimeout(500, event ->
+ {
+ HttpServletResponse response = (HttpServletResponse)event.getAsyncContext().getResponse();
+ response.sendError(HttpStatus.BAD_GATEWAY_502);
+ });
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 502 "));
+ }
+
+ @Test
+ public void test_StartAsync_OnTimeout_SendError_CustomErrorPage() throws Exception
+ {
+ test_StartAsync_OnTimeout(500, event ->
+ {
+ AsyncContext asyncContext = event.getAsyncContext();
+ HttpServletResponse response = (HttpServletResponse)asyncContext.getResponse();
+ response.sendError(HttpStatus.BAD_GATEWAY_502);
+ asyncContext.complete();
+ });
+
+ // Add a custom error page.
+ ErrorPageErrorHandler errorHandler = new ErrorPageErrorHandler();
+ errorHandler.setServer(server);
+ errorHandler.addErrorPage(HttpStatus.BAD_GATEWAY_502, "/error");
+ server.addManaged(errorHandler);
+
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 502 "));
+ assertThat(httpResponse, containsString("CUSTOM"));
+ }
+
+ private void test_StartAsync_OnTimeout(long timeout, IOConsumer consumer) throws Exception
+ {
+ ServletContextHandler context = new ServletContextHandler();
+ context.addServlet(new ServletHolder(new HttpServlet()
+ {
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- AsyncContext ctx = req.startAsync();
- ctx.setTimeout(1000);
- ctx.addListener(new AsyncListenerAdapter()
+ AsyncContext asyncContext = request.startAsync();
+ asyncContext.setTimeout(timeout);
+ asyncContext.addListener(new AsyncListenerAdapter()
{
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
- throw new FooRuntimeException();
+ consumer.accept(event);
}
});
}
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
-
- server.setHandler(context);
-
- try
+ }), "/*");
+ context.addServlet(new ServletHolder(new HttpServlet()
{
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
- }
- finally
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.setStatus(HttpStatus.OK_200);
+ }
+ }), "/dispatch/*");
+ context.addServlet(new ServletHolder(new HttpServlet()
{
- server.stop();
- }
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.getOutputStream().print("CUSTOM");
+ }
+ }), "/error/*");
+
+ startServer(context);
}
- /**
- * async testcase of error handling from a filter.
- *
- * Async Started, no listener, in start() throw Exception
- *
- * @throws Exception
- * on test failure
- */
@Test
- public void testFilterErrorAsyncStart_NoListener_ExceptionDuringStart() throws Exception
+ public void test_StartAsync_OnComplete_Throw() throws Exception
{
- Server server = new Server();
- LocalConnector conn = new LocalConnector(server);
- conn.setIdleTimeout(10000);
- server.addConnector(conn);
-
ServletContextHandler context = new ServletContextHandler();
- context.setContextPath("/");
- @SuppressWarnings("serial")
- HttpServlet servlet = new HttpServlet()
+ context.addServlet(new ServletHolder(new HttpServlet()
{
- public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ @Override
+ protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
- AsyncContext ctx = req.startAsync();
- ctx.setTimeout(1000);
- ctx.start(new Runnable()
+ AsyncContext asyncContext = request.startAsync();
+ asyncContext.setTimeout(0);
+ asyncContext.addListener(new AsyncListenerAdapter()
{
@Override
- public void run()
+ public void onComplete(AsyncEvent event) throws IOException
{
- throw new FooRuntimeException();
+ throw new TestRuntimeException();
}
});
+ response.getOutputStream().print("DATA");
+ asyncContext.complete();
}
- };
- ServletHolder holder = new ServletHolder(servlet);
- holder.setAsyncSupported(true);
- context.addServlet(holder,"/err/*");
- List tracking = new LinkedList();
- ErrorFilter filter = new ErrorFilter(tracking);
- context.addFilter(new FilterHolder(filter),"/*",EnumSet.allOf(DispatcherType.class));
+ }), "/*");
- server.setHandler(context);
+ startServer(context);
- try
+ String httpResponse = connector.getResponses("" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+ assertThat(httpResponse, containsString("HTTP/1.1 200 "));
+ assertThat(httpResponse, containsString("DATA"));
+ }
+
+
+ // Unique named RuntimeException to help during debugging / assertions.
+ public static class TestRuntimeException extends RuntimeException
+ {
+ }
+
+ public static class AsyncListenerAdapter implements AsyncListener
+ {
+ @Override
+ public void onComplete(AsyncEvent event) throws IOException
{
- server.start();
- String resp = conn.getResponses("GET /err/ HTTP/1.1\n" + "Host: localhost\n" + "Connection: close\n" + "\n");
- assertThat("Response status",resp,containsString("HTTP/1.1 500 Server Error"));
- assertThat("Response",resp,containsString(FooRuntimeException.class.getName()));
}
- finally
+
+ @Override
+ public void onTimeout(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onError(AsyncEvent event) throws IOException
+ {
+ }
+
+ @Override
+ public void onStartAsync(AsyncEvent event) throws IOException
{
- server.stop();
}
}
+
+ @FunctionalInterface
+ private interface IOConsumer
+ {
+ void accept(T t) throws IOException;
+ }
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
index 0326d44a47a..1095b9f0873 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java
@@ -18,9 +18,14 @@
package org.eclipse.jetty.servlet;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.IOException;
@@ -50,12 +55,14 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
+import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -68,6 +75,7 @@ public class AsyncServletIOTest
protected AsyncIOServlet _servlet0=new AsyncIOServlet();
protected AsyncIOServlet2 _servlet2=new AsyncIOServlet2();
protected AsyncIOServlet3 _servlet3=new AsyncIOServlet3();
+ protected AsyncIOServlet4 _servlet4=new AsyncIOServlet4();
protected int _port;
protected Server _server = new Server();
protected ServletHandler _servletHandler;
@@ -83,6 +91,7 @@ public class AsyncServletIOTest
_server.setConnectors(new Connector[]{ _connector });
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/ctx");
+ context.addEventListener(new DebugListener());
_server.setHandler(context);
_servletHandler=context.getServletHandler();
@@ -99,6 +108,10 @@ public class AsyncServletIOTest
holder3.setAsyncSupported(true);
_servletHandler.addServletWithMapping(holder3,"/path3/*");
+ ServletHolder holder4=new ServletHolder(_servlet4);
+ holder4.setAsyncSupported(true);
+ _servletHandler.addServletWithMapping(holder4,"/path4/*");
+
_server.start();
_port=_connector.getLocalPort();
@@ -230,7 +243,7 @@ public class AsyncServletIOTest
int port=_port;
try (Socket socket = new Socket("localhost",port))
{
- socket.setSoTimeout(1000000);
+ socket.setSoTimeout(10000);
OutputStream out = socket.getOutputStream();
out.write(request.toString().getBytes(StandardCharsets.ISO_8859_1));
@@ -261,6 +274,8 @@ public class AsyncServletIOTest
}
}
+
+
public synchronized List process(String content,int... writes) throws Exception
{
return process(content.getBytes(StandardCharsets.ISO_8859_1),writes);
@@ -594,4 +609,173 @@ public class AsyncServletIOTest
async.complete();
}
}
+
+
+ @Test
+ public void testCompleteWhilePending() throws Exception
+ {
+ StringBuilder request = new StringBuilder(512);
+ request.append("POST /ctx/path4/info HTTP/1.1\r\n")
+ .append("Host: localhost\r\n")
+ .append("Content-Type: text/plain\r\n")
+ .append("Content-Length: 20\r\n")
+ .append("\r\n")
+ .append("12345678\r\n");
+
+ int port=_port;
+ List list = new ArrayList<>();
+ try (Socket socket = new Socket("localhost",port))
+ {
+ socket.setSoTimeout(10000);
+ OutputStream out = socket.getOutputStream();
+ out.write(request.toString().getBytes(ISO_8859_1));
+ out.flush();
+ Thread.sleep(100);
+ out.write("ABC".getBytes(ISO_8859_1));
+ out.flush();
+ Thread.sleep(100);
+ out.write("DEF".getBytes(ISO_8859_1));
+ out.flush();
+
+ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
+
+ // response line
+ String line = in.readLine();
+ LOG.debug("response-line: "+line);
+ Assert.assertThat(line,startsWith("HTTP/1.1 200 OK"));
+
+ boolean chunked=false;
+ // Skip headers
+ while (line!=null)
+ {
+ line = in.readLine();
+ LOG.debug("header-line: "+line);
+ chunked|="Transfer-Encoding: chunked".equals(line);
+ if (line.length()==0)
+ break;
+ }
+
+ assertTrue(chunked);
+
+ // Get body slowly
+ String last;
+ while (true)
+ {
+ last=line;
+ //Thread.sleep(1000);
+ line = in.readLine();
+ LOG.debug("body: "+line);
+ if (line==null)
+ break;
+ list.add(line);
+ }
+
+ LOG.debug("last: "+last);
+ // last non empty line should contain some X's
+ assertThat(last,containsString("X"));
+ // last non empty line should not contain end chunk
+ assertThat(last,not(containsString("0")));
+ }
+
+ assertTrue(_servlet4.completed.await(5, TimeUnit.SECONDS));
+ Thread.sleep(100);
+ assertEquals(2,_servlet4.onDA.get());
+ assertEquals(2,_servlet4.onWP.get());
+
+
+ }
+
+ @SuppressWarnings("serial")
+ public class AsyncIOServlet4 extends HttpServlet
+ {
+ public CountDownLatch completed = new CountDownLatch(1);
+ public AtomicInteger onDA = new AtomicInteger();
+ public AtomicInteger onWP = new AtomicInteger();
+
+ @Override
+ public void doPost(final HttpServletRequest request, final HttpServletResponse response) throws IOException
+ {
+ final AsyncContext async = request.startAsync();
+ final ServletInputStream in = request.getInputStream();
+ final ServletOutputStream out = response.getOutputStream();
+
+ in.setReadListener(new ReadListener()
+ {
+
+ @Override
+ public void onError(Throwable t)
+ {
+ t.printStackTrace();
+ }
+
+ @Override
+ public void onDataAvailable() throws IOException
+ {
+ onDA.incrementAndGet();
+ if (onDA.get()>2)
+ return;
+
+ // Read all available content
+ while(in.isReady())
+ if (in.read()<0)
+ throw new IllegalStateException();
+
+ if (onDA.get()==1)
+ return;
+
+ final byte[] buffer = new byte[64*1024];
+ Arrays.fill(buffer,(byte)'X');
+ for (int i=199;i2)
+ return;
+
+ while (out.isReady())
+ out.write(buffer);
+
+ if (onWP.get()==1)
+ return;
+
+ try
+ {
+ // As soon as we are write blocked, complete
+ async.complete();
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ completed.countDown();
+ }
+ }
+
+ @Override
+ public void onError(Throwable t)
+ {
+ t.printStackTrace();
+ }
+ });
+ }
+
+ @Override
+ public void onAllDataRead() throws IOException
+ {
+ throw new IllegalStateException();
+ }
+ });
+
+ }
+ }
+
+
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
index 3c076af5849..e07acc9d916 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletTest.java
@@ -44,6 +44,7 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.QuietServletException;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
@@ -77,6 +78,7 @@ public class AsyncServletTest
protected Server _server = new Server();
protected ServletHandler _servletHandler;
+ protected ErrorPageErrorHandler _errorHandler;
protected ServerConnector _connector;
protected List _log;
protected int _expectedLogs;
@@ -84,6 +86,12 @@ public class AsyncServletTest
protected static List __history=new CopyOnWriteArrayList<>();
protected static CountDownLatch __latch;
+ static void historyAdd(String item)
+ {
+ // System.err.println(Thread.currentThread()+" history: "+item);
+ __history.add(item);
+ }
+
@Before
public void setUp() throws Exception
{
@@ -101,10 +109,17 @@ public class AsyncServletTest
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/ctx");
logHandler.setHandler(context);
+ context.addEventListener(new DebugListener());
+
+ _errorHandler = new ErrorPageErrorHandler();
+ context.setErrorHandler(_errorHandler);
+ _errorHandler.addErrorPage(300,599,"/error/custom");
+
_servletHandler=context.getServletHandler();
ServletHolder holder=new ServletHolder(_servlet);
holder.setAsyncSupported(true);
+ _servletHandler.addServletWithMapping(holder,"/error/*");
_servletHandler.addServletWithMapping(holder,"/path/*");
_servletHandler.addServletWithMapping(holder,"/path1/*");
_servletHandler.addServletWithMapping(holder,"/path2/*");
@@ -167,17 +182,17 @@ public class AsyncServletTest
{
_expectedCode="500 ";
String response=process("start=200",null);
- Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 500 Async Timeout"));
+ Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 500 Server Error"));
assertThat(__history,contains(
"REQUEST /ctx/path/info",
"initial",
"start",
"onTimeout",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/error/custom",
"!initial",
"onComplete"));
- assertContains("ERROR DISPATCH: /ctx/path/info",response);
+ assertContains("ERROR DISPATCH: /ctx/error/custom",response);
}
@Test
@@ -211,7 +226,7 @@ public class AsyncServletTest
"onTimeout",
"error",
"onError",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/error/custom",
"!initial",
"onComplete"));
@@ -314,10 +329,10 @@ public class AsyncServletTest
"initial",
"start",
"onError",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/error/custom",
"!initial",
"onComplete"));
- assertContains("ERROR DISPATCH: /ctx/path/info",response);
+ assertContains("ERROR DISPATCH: /ctx/error/custom",response);
}
@Test
@@ -397,7 +412,7 @@ public class AsyncServletTest
{
_expectedCode="500 ";
String response=process("start=1000&dispatch=10&start2=10",null);
- assertEquals("HTTP/1.1 500 Async Timeout",response.substring(0,26));
+ assertThat(response,startsWith("HTTP/1.1 500 Server Error"));
assertThat(__history,contains(
"REQUEST /ctx/path/info",
"initial",
@@ -408,10 +423,10 @@ public class AsyncServletTest
"onStartAsync",
"start",
"onTimeout",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/error/custom",
"!initial",
"onComplete"));
- assertContains("ERROR DISPATCH: /ctx/path/info",response);
+ assertContains("ERROR DISPATCH: /ctx/error/custom",response);
}
@Test
@@ -424,7 +439,7 @@ public class AsyncServletTest
"initial",
"start",
"onTimeout",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/error/custom",
"!initial",
"onStartAsync",
"start",
@@ -445,7 +460,7 @@ public class AsyncServletTest
"initial",
"start",
"onTimeout",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/error/custom",
"!initial",
"onStartAsync",
"start",
@@ -458,21 +473,23 @@ public class AsyncServletTest
public void testStartTimeoutStart() throws Exception
{
_expectedCode="500 ";
+ _errorHandler.addErrorPage(500,"/path/error");
+
String response=process("start=10&start2=10",null);
assertThat(__history,contains(
"REQUEST /ctx/path/info",
"initial",
"start",
"onTimeout",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/path/error",
"!initial",
"onStartAsync",
"start",
"onTimeout",
- "ERROR /ctx/path/info",
+ "ERROR /ctx/path/error",
"!initial",
"onComplete"));
- assertContains("ERROR DISPATCH: /ctx/path/info",response);
+ assertContains("ERROR DISPATCH: /ctx/path/error",response);
}
@Test
@@ -671,9 +688,9 @@ public class AsyncServletTest
@Override
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
- __history.add("FWD "+request.getDispatcherType()+" "+request.getRequestURI());
+ historyAdd("FWD "+request.getDispatcherType()+" "+request.getRequestURI());
if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper)
- __history.add("wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":""));
+ historyAdd("wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":""));
request.getServletContext().getRequestDispatcher("/path1").forward(request,response);
}
}
@@ -698,9 +715,9 @@ public class AsyncServletTest
}
// System.err.println(request.getDispatcherType()+" "+request.getRequestURI());
- __history.add(request.getDispatcherType()+" "+request.getRequestURI());
+ historyAdd(request.getDispatcherType()+" "+request.getRequestURI());
if (request instanceof ServletRequestWrapper || response instanceof ServletResponseWrapper)
- __history.add("wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":""));
+ historyAdd("wrapped"+((request instanceof ServletRequestWrapper)?" REQ":"")+((response instanceof ServletResponseWrapper)?" RSP":""));
boolean wrap="true".equals(request.getParameter("wrap"));
int read_before=0;
@@ -734,7 +751,7 @@ public class AsyncServletTest
if (request.getAttribute("State")==null)
{
request.setAttribute("State",new Integer(1));
- __history.add("initial");
+ historyAdd("initial");
if (read_before>0)
{
byte[] buf=new byte[read_before];
@@ -762,7 +779,7 @@ public class AsyncServletTest
while(b!=-1)
if((b=in.read())>=0)
c++;
- __history.add("async-read="+c);
+ historyAdd("async-read="+c);
}
catch(Exception e)
{
@@ -778,7 +795,7 @@ public class AsyncServletTest
if (start_for>0)
async.setTimeout(start_for);
async.addListener(__listener);
- __history.add("start");
+ historyAdd("start");
if ("1".equals(request.getParameter("throw")))
throw new QuietServletException(new Exception("test throw in async 1"));
@@ -794,7 +811,7 @@ public class AsyncServletTest
{
response.setStatus(200);
response.getOutputStream().println("COMPLETED\n");
- __history.add("complete");
+ historyAdd("complete");
async.complete();
}
catch(Exception e)
@@ -812,7 +829,7 @@ public class AsyncServletTest
{
response.setStatus(200);
response.getOutputStream().println("COMPLETED\n");
- __history.add("complete");
+ historyAdd("complete");
async.complete();
}
else if (dispatch_after>0)
@@ -822,7 +839,7 @@ public class AsyncServletTest
@Override
public void run()
{
- __history.add("dispatch");
+ historyAdd("dispatch");
if (path!=null)
{
int q=path.indexOf('?');
@@ -842,7 +859,7 @@ public class AsyncServletTest
}
else if (dispatch_after==0)
{
- __history.add("dispatch");
+ historyAdd("dispatch");
if (path!=null)
async.dispatch(path);
else
@@ -871,7 +888,7 @@ public class AsyncServletTest
}
else
{
- __history.add("!initial");
+ historyAdd("!initial");
if (start2_for>=0 && request.getAttribute("2nd")==null)
{
@@ -883,7 +900,7 @@ public class AsyncServletTest
{
async.setTimeout(start2_for);
}
- __history.add("start");
+ historyAdd("start");
if ("2".equals(request.getParameter("throw")))
throw new QuietServletException(new Exception("test throw in async 2"));
@@ -899,7 +916,7 @@ public class AsyncServletTest
{
response.setStatus(200);
response.getOutputStream().println("COMPLETED\n");
- __history.add("complete");
+ historyAdd("complete");
async.complete();
}
catch(Exception e)
@@ -917,7 +934,7 @@ public class AsyncServletTest
{
response.setStatus(200);
response.getOutputStream().println("COMPLETED\n");
- __history.add("complete");
+ historyAdd("complete");
async.complete();
}
else if (dispatch2_after>0)
@@ -927,7 +944,7 @@ public class AsyncServletTest
@Override
public void run()
{
- __history.add("dispatch");
+ historyAdd("dispatch");
async.dispatch();
}
};
@@ -938,7 +955,7 @@ public class AsyncServletTest
}
else if (dispatch2_after==0)
{
- __history.add("dispatch");
+ historyAdd("dispatch");
async.dispatch();
}
}
@@ -961,11 +978,11 @@ public class AsyncServletTest
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
- __history.add("onTimeout");
+ historyAdd("onTimeout");
String action=event.getSuppliedRequest().getParameter("timeout");
if (action!=null)
{
- __history.add(action);
+ historyAdd(action);
switch(action)
{
@@ -987,17 +1004,17 @@ public class AsyncServletTest
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
- __history.add("onStartAsync");
+ historyAdd("onStartAsync");
}
@Override
public void onError(AsyncEvent event) throws IOException
{
- __history.add("onError");
+ historyAdd("onError");
String action=event.getSuppliedRequest().getParameter("error");
if (action!=null)
{
- __history.add(action);
+ historyAdd(action);
switch(action)
{
@@ -1016,7 +1033,7 @@ public class AsyncServletTest
@Override
public void onComplete(AsyncEvent event) throws IOException
{
- __history.add("onComplete");
+ historyAdd("onComplete");
__latch.countDown();
}
};
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
index 7c58a0aadb1..0f740d9da92 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java
@@ -25,6 +25,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.EnumSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -99,7 +100,7 @@ public class DefaultServletTest
testdir.ensureEmpty();
/* create some content in the docroot */
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
assertTrue(resBase.mkdirs());
assertTrue(new File(resBase, "one").mkdir());
assertTrue(new File(resBase, "two").mkdir());
@@ -131,7 +132,7 @@ public class DefaultServletTest
testdir.ensureEmpty();
/* create some content in the docroot */
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
assertTrue(new File(resBase, "one").mkdir());
assertTrue(new File(resBase, "two").mkdir());
@@ -168,7 +169,7 @@ public class DefaultServletTest
testdir.ensureEmpty();
/* create some content in the docroot */
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
assertTrue(resBase.mkdirs());
File wackyDir = new File(resBase, "dir;"); // this should not be double-encoded.
assertTrue(wackyDir.mkdirs());
@@ -220,7 +221,7 @@ public class DefaultServletTest
testdir.ensureEmpty();
/* create some content in the docroot */
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
assertTrue(resBase.mkdirs());
File index = new File(resBase, "index.html");
@@ -320,7 +321,7 @@ public class DefaultServletTest
public void testWelcome() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File inde = new File(resBase, "index.htm");
File index = new File(resBase, "index.html");
@@ -364,7 +365,7 @@ public class DefaultServletTest
public void testWelcomeServlet() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File inde = new File(resBase, "index.htm");
File index = new File(resBase, "index.html");
@@ -409,13 +410,18 @@ public class DefaultServletTest
}
@Test
- public void testResourceBase() throws Exception
+ public void testSymLinks() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
- File foobar = new File(resBase, "foobar.txt");
- File link = new File(resBase, "link.txt");
+ File dir = new File(resBase,"dir");
+ File dirLink = new File(resBase,"dirlink");
+ File dirRLink = new File(resBase,"dirrlink");
+ FS.ensureDirExists(dir);
+ File foobar = new File(dir, "foobar.txt");
+ File link = new File(dir, "link.txt");
+ File rLink = new File(dir,"rlink.txt");
createFile(foobar, "Foo Bar");
String resBasePath = resBase.getAbsolutePath();
@@ -426,20 +432,43 @@ public class DefaultServletTest
String response;
- response = connector.getResponses("GET /context/foobar.txt HTTP/1.0\r\n\r\n");
+ response = connector.getResponses("GET /context/dir/foobar.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Foo Bar", response);
if (!OS.IS_WINDOWS)
{
context.clearAliasChecks();
+ Files.createSymbolicLink(dirLink.toPath(),dir.toPath());
+ Files.createSymbolicLink(dirRLink.toPath(),new File("dir").toPath());
Files.createSymbolicLink(link.toPath(),foobar.toPath());
- response = connector.getResponses("GET /context/link.txt HTTP/1.0\r\n\r\n");
+ Files.createSymbolicLink(rLink.toPath(),new File("foobar.txt").toPath());
+ response = connector.getResponses("GET /context/dir/link.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("404", response);
+ response = connector.getResponses("GET /context/dir/rlink.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("404", response);
+ response = connector.getResponses("GET /context/dirlink/foobar.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("404", response);
+ response = connector.getResponses("GET /context/dirrlink/foobar.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("404", response);
+ response = connector.getResponses("GET /context/dirlink/link.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("404", response);
+ response = connector.getResponses("GET /context/dirrlink/rlink.txt HTTP/1.0\r\n\r\n");
assertResponseContains("404", response);
context.addAliasCheck(new AllowSymLinkAliasChecker());
- response = connector.getResponses("GET /context/link.txt HTTP/1.0\r\n\r\n");
+ response = connector.getResponses("GET /context/dir/link.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("Foo Bar", response);
+ response = connector.getResponses("GET /context/dir/rlink.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("Foo Bar", response);
+ response = connector.getResponses("GET /context/dirlink/foobar.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("Foo Bar", response);
+ response = connector.getResponses("GET /context/dirrlink/foobar.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("Foo Bar", response);
+ response = connector.getResponses("GET /context/dirlink/link.txt HTTP/1.0\r\n\r\n");
+ assertResponseContains("Foo Bar", response);
+ response = connector.getResponses("GET /context/dirrlink/link.txt HTTP/1.0\r\n\r\n");
assertResponseContains("Foo Bar", response);
}
}
@@ -448,7 +477,7 @@ public class DefaultServletTest
public void testWelcomeExactServlet() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File inde = new File(resBase, "index.htm");
File index = new File(resBase, "index.html");
@@ -496,7 +525,7 @@ public class DefaultServletTest
public void testRangeRequests() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File data = new File(resBase, "data.txt");
createFile(data, "01234567890123456789012345678901234567890123456789012345678901234567890123456789");
@@ -643,7 +672,7 @@ public class DefaultServletTest
public void testFiltered() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File file0 = new File(resBase, "data0.txt");
createFile(file0, "Hello Text 0");
@@ -687,7 +716,7 @@ public class DefaultServletTest
public void testGzip() throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File file0 = new File(resBase, "data0.txt");
createFile(file0, "Hello Text 0");
@@ -701,6 +730,7 @@ public class DefaultServletTest
defholder.setInitParameter("redirectWelcome", "false");
defholder.setInitParameter("welcomeServlets", "false");
defholder.setInitParameter("gzip", "true");
+ defholder.setInitParameter("etags", "true");
defholder.setInitParameter("resourceBase", resBasePath);
String response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\n\r\n");
@@ -708,7 +738,11 @@ public class DefaultServletTest
assertResponseContains("Content-Type: text/plain",response);
assertResponseContains("Hello Text 0",response);
assertResponseContains("Vary: Accept-Encoding",response);
+ assertResponseContains("ETag: ",response);
assertResponseNotContains("Content-Encoding: gzip",response);
+ int e=response.indexOf("ETag: ");
+ String etag = response.substring(e+6,response.indexOf('"',e+11)+1);
+ String etag_gzip = etag.substring(0,etag.length()-1)+"--gzip\"";
response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
assertResponseContains("Content-Length: 9", response);
@@ -716,10 +750,112 @@ public class DefaultServletTest
assertResponseContains("Content-Type: text/plain",response);
assertResponseContains("Vary: Accept-Encoding",response);
assertResponseContains("Content-Encoding: gzip",response);
+ assertResponseContains("ETag: "+etag_gzip,response);
+
+ response = connector.getResponses("GET /context/data0.txt.gz HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
+ assertResponseContains("Content-Length: 9", response);
+ assertResponseContains("fake gzip",response);
+ assertResponseContains("Content-Type: application/gzip",response);
+ assertResponseNotContains("Vary: Accept-Encoding",response);
+ assertResponseNotContains("Content-Encoding: gzip",response);
+ assertResponseNotContains("ETag: "+etag_gzip,response);
+ assertResponseContains("ETag: ",response);
+
+ response = connector.getResponses("GET /context/data0.txt.gz HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: W/\"wobble\"\r\n\r\n");
+ assertResponseContains("Content-Length: 9", response);
+ assertResponseContains("fake gzip",response);
+ assertResponseContains("Content-Type: application/gzip",response);
+ assertResponseNotContains("Vary: Accept-Encoding",response);
+ assertResponseNotContains("Content-Encoding: gzip",response);
+ assertResponseNotContains("ETag: "+etag_gzip,response);
+ assertResponseContains("ETag: ",response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: "+etag_gzip+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag_gzip,response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: "+etag+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag,response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: W/\"foobar\","+etag_gzip+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag_gzip,response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: W/\"foobar\","+etag+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag,response);
}
+ @Test
+ public void testCachedGzip() throws Exception
+ {
+ testdir.ensureEmpty();
+ File resBase = testdir.getPathFile("docroot").toFile();
+ FS.ensureDirExists(resBase);
+ File file0 = new File(resBase, "data0.txt");
+ createFile(file0, "Hello Text 0");
+ File file0gz = new File(resBase, "data0.txt.gz");
+ createFile(file0gz, "fake gzip");
+ String resBasePath = resBase.getAbsolutePath();
+
+ ServletHolder defholder = context.addServlet(DefaultServlet.class, "/");
+ defholder.setInitParameter("dirAllowed", "false");
+ defholder.setInitParameter("redirectWelcome", "false");
+ defholder.setInitParameter("welcomeServlets", "false");
+ defholder.setInitParameter("gzip", "true");
+ defholder.setInitParameter("etags", "true");
+ defholder.setInitParameter("resourceBase", resBasePath);
+ defholder.setInitParameter("maxCachedFiles", "1024");
+ defholder.setInitParameter("maxCachedFileSize", "200000000");
+ defholder.setInitParameter("maxCacheSize", "256000000");
+
+ String response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\n\r\n");
+ assertResponseContains("Content-Length: 12", response);
+ assertResponseContains("Content-Type: text/plain",response);
+ assertResponseContains("Hello Text 0",response);
+ assertResponseContains("Vary: Accept-Encoding",response);
+ assertResponseContains("ETag: ",response);
+ assertResponseNotContains("Content-Encoding: gzip",response);
+ int e=response.indexOf("ETag: ");
+ String etag = response.substring(e+6,response.indexOf('"',e+11)+1);
+ String etag_gzip = etag.substring(0,etag.length()-1)+"--gzip\"";
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
+ assertResponseContains("Content-Length: 9", response);
+ assertResponseContains("fake gzip",response);
+ assertResponseContains("Content-Type: text/plain",response);
+ assertResponseContains("Vary: Accept-Encoding",response);
+ assertResponseContains("Content-Encoding: gzip",response);
+ assertResponseContains("ETag: "+etag_gzip,response);
+
+ response = connector.getResponses("GET /context/data0.txt.gz HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\n\r\n");
+ assertResponseContains("Content-Length: 9", response);
+ assertResponseContains("fake gzip",response);
+ assertResponseContains("Content-Type: application/gzip",response);
+ assertResponseNotContains("Vary: Accept-Encoding",response);
+ assertResponseNotContains("Content-Encoding: gzip",response);
+ assertResponseNotContains("ETag: "+etag_gzip,response);
+ assertResponseContains("ETag: ",response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: "+etag_gzip+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag_gzip,response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: "+etag+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag,response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: W/\"foobar\","+etag_gzip+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag_gzip,response);
+
+ response = connector.getResponses("GET /context/data0.txt HTTP/1.0\r\nHost:localhost:8080\r\nAccept-Encoding:gzip\r\nIf-None-Match: W/\"foobar\","+etag+"\r\n\r\n");
+ assertResponseContains("304 Not Modified", response);
+ assertResponseContains("ETag: "+etag,response);
+ }
@Test
public void testIfModifiedSmall() throws Exception
@@ -736,7 +872,7 @@ public class DefaultServletTest
public void testIfModified(String content) throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File file = new File(resBase, "file.txt");
@@ -789,7 +925,7 @@ public class DefaultServletTest
public void testIfETag(String content) throws Exception
{
testdir.ensureEmpty();
- File resBase = testdir.getFile("docroot");
+ File resBase = testdir.getPathFile("docroot").toFile();
FS.ensureDirExists(resBase);
File file = new File(resBase, "file.txt");
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java
index ec1f6a5d7ec..51e9f0d910d 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherForwardTest.java
@@ -21,6 +21,8 @@ package org.eclipse.jetty.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
@@ -36,6 +38,8 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
+
+@SuppressWarnings("serial")
public class DispatcherForwardTest
{
private Server server;
@@ -81,12 +85,13 @@ public class DispatcherForwardTest
@Test
public void testQueryRetainedByForwardWithoutQuery() throws Exception
{
- // 1. request /one?a=1
+ // 1. request /one?a=1%20one
// 1. forward /two
- // 2. assert query => a=1
- // 1. assert query => a=1
+ // 2. assert query => a=1 one
+ // 1. assert query => a=1 one
- final String query1 = "a=1";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
servlet1 = new HttpServlet()
{
@Override
@@ -97,7 +102,8 @@ public class DispatcherForwardTest
req.getRequestDispatcher("/two").forward(req, resp);
checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -106,7 +112,7 @@ public class DispatcherForwardTest
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
}
};
@@ -118,6 +124,7 @@ public class DispatcherForwardTest
"Connection: close\r\n" +
"\r\n";
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@@ -129,9 +136,10 @@ public class DispatcherForwardTest
// 2. assert query => a=2
// 1. assert query => a=1
- final String query1 = "a=1&b=2";
- final String query2 = "a=3";
- final String query3 = "a=3&b=2";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one&b=2%20two";
+ final String query2 = "a=3%20three";
+ final String query3 = "a=3%20three&b=2%20two";
servlet1 = new HttpServlet()
{
@Override
@@ -141,9 +149,10 @@ public class DispatcherForwardTest
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
- checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
+ checkThat(req.getQueryString(), Matchers.equalTo(query1));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -152,8 +161,8 @@ public class DispatcherForwardTest
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(req.getQueryString(),Matchers.equalTo(query3));
- checkThat(req.getParameter("a"),Matchers.equalTo("3"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("3 three"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
}
};
@@ -165,6 +174,7 @@ public class DispatcherForwardTest
"Connection: close\r\n" +
"\r\n";
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@@ -176,9 +186,10 @@ public class DispatcherForwardTest
// 2. assert query => a=1&b=2
// 1. assert query => a=1
- final String query1 = "a=1";
- final String query2 = "b=2";
- final String query3 = "b=2&a=1";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String query2 = "b=2%20two";
+ final String query3 = "b=2%20two&a=1%20one";
servlet1 = new HttpServlet()
{
@Override
@@ -189,7 +200,8 @@ public class DispatcherForwardTest
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -198,8 +210,8 @@ public class DispatcherForwardTest
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(req.getQueryString(),Matchers.equalTo(query3));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
}
};
@@ -211,6 +223,7 @@ public class DispatcherForwardTest
"Connection: close\r\n" +
"\r\n";
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@@ -222,8 +235,9 @@ public class DispatcherForwardTest
// 2. assert query => a=1 + params => a=1,2
// 1. assert query => a=1 + params => a=1,2
- final String query1 = "a=1";
- final String form = "a=2";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String form = "a=2%20two";
servlet1 = new HttpServlet()
{
@Override
@@ -237,7 +251,8 @@ public class DispatcherForwardTest
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(2, Matchers.equalTo(values.length));
- checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
+ checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -249,7 +264,7 @@ public class DispatcherForwardTest
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(2, Matchers.equalTo(values.length));
- checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
+ checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
}
};
@@ -264,6 +279,7 @@ public class DispatcherForwardTest
"\r\n" +
form;
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@@ -275,9 +291,10 @@ public class DispatcherForwardTest
// 2. assert query => a=3 + params => a=3,2,1
// 1. assert query => a=1 + params => a=1,2
- final String query1 = "a=1";
- final String query2 = "a=3";
- final String form = "a=2";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String query2 = "a=3%20three";
+ final String form = "a=2%20two";
servlet1 = new HttpServlet()
{
@Override
@@ -291,7 +308,8 @@ public class DispatcherForwardTest
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(2, Matchers.equalTo(values.length));
- checkThat(values, Matchers.arrayContainingInAnyOrder("1", "2"));
+ checkThat(values, Matchers.arrayContainingInAnyOrder("1 one", "2 two"));
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -303,7 +321,7 @@ public class DispatcherForwardTest
String[] values = req.getParameterValues("a");
checkThat(values, Matchers.notNullValue());
checkThat(3, Matchers.equalTo(values.length));
- checkThat(values, Matchers.arrayContainingInAnyOrder("3", "2", "1"));
+ checkThat(values, Matchers.arrayContainingInAnyOrder("3 three", "2 two", "1 one"));
}
};
@@ -318,6 +336,7 @@ public class DispatcherForwardTest
"\r\n" +
form;
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@@ -329,10 +348,11 @@ public class DispatcherForwardTest
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
// 1. assert query => a=1 + params => a=1&b=2
- final String query1 = "a=1";
- final String query2 = "c=3";
- final String query3 = "c=3&a=1";
- final String form = "b=2";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String query2 = "c=3%20three";
+ final String query3 = "c=3%20three&a=1%20one";
+ final String form = "b=2%20two";
servlet1 = new HttpServlet()
{
@Override
@@ -343,9 +363,10 @@ public class DispatcherForwardTest
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
checkThat(req.getParameter("c"), Matchers.nullValue());
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -354,9 +375,9 @@ public class DispatcherForwardTest
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(req.getQueryString(),Matchers.equalTo(query3));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
- checkThat(req.getParameter("c"),Matchers.equalTo("3"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
+ checkThat(req.getParameter("c"),Matchers.equalTo("3 three"));
}
};
@@ -371,6 +392,7 @@ public class DispatcherForwardTest
"\r\n" +
form;
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@@ -383,25 +405,27 @@ public class DispatcherForwardTest
// 2. assert query => a=1&c=3 + params => a=1&b=2&c=3
// 1. assert query => a=1 + params => a=1&b=2
- final String query1 = "a=1";
- final String query2 = "c=3";
- final String query3 = "c=3&a=1";
- final String form = "b=2";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String query2 = "c=3%20three";
+ final String query3 = "c=3%20three&a=1%20one";
+ final String form = "b=2%20two";
servlet1 = new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
req.getRequestDispatcher("/two?" + query2).forward(req, resp);
checkThat(req.getQueryString(),Matchers.equalTo(query1));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
checkThat(req.getParameter("c"), Matchers.nullValue());
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -410,9 +434,9 @@ public class DispatcherForwardTest
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
checkThat(req.getQueryString(),Matchers.equalTo(query3));
- checkThat(req.getParameter("a"),Matchers.equalTo("1"));
- checkThat(req.getParameter("b"),Matchers.equalTo("2"));
- checkThat(req.getParameter("c"),Matchers.equalTo("3"));
+ checkThat(req.getParameter("a"),Matchers.equalTo("1 one"));
+ checkThat(req.getParameter("b"),Matchers.equalTo("2 two"));
+ checkThat(req.getParameter("c"),Matchers.equalTo("3 three"));
}
};
@@ -427,14 +451,16 @@ public class DispatcherForwardTest
"\r\n" +
form;
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testContentCanBeReadViaInputStreamAfterForwardWithoutQuery() throws Exception
{
- final String query1 = "a=1";
- final String form = "c=3";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String form = "c=3%20three";
servlet1 = new HttpServlet()
{
@Override
@@ -446,6 +472,7 @@ public class DispatcherForwardTest
checkThat(req.getQueryString(),Matchers.equalTo(query1));
checkThat(req.getParameter("c"), Matchers.nullValue());
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -471,16 +498,18 @@ public class DispatcherForwardTest
"\r\n" +
form;
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
@Test
public void testContentCanBeReadViaInputStreamAfterForwardWithQuery() throws Exception
{
- final String query1 = "a=1";
- final String query2 = "b=2";
- final String query3 = "b=2&a=1";
- final String form = "c=3";
+ CountDownLatch latch = new CountDownLatch(1);
+ final String query1 = "a=1%20one";
+ final String query2 = "b=2%20two";
+ final String query3 = "b=2%20two&a=1%20one";
+ final String form = "c=3%20three";
servlet1 = new HttpServlet()
{
@Override
@@ -492,6 +521,7 @@ public class DispatcherForwardTest
checkThat(req.getQueryString(),Matchers.equalTo(query1));
checkThat(req.getParameter("c"), Matchers.nullValue());
+ latch.countDown();
}
};
servlet2 = new HttpServlet()
@@ -518,6 +548,7 @@ public class DispatcherForwardTest
"\r\n" +
form;
String response = connector.getResponses(request);
+ Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(response, response.startsWith("HTTP/1.1 200"));
}
diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
index 363f5c5a352..cc36ddf8878 100644
--- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ErrorPageTest.java
@@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.LocalConnector;
+import org.eclipse.jetty.server.QuietServletException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
similarity index 53%
rename from examples/embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java
rename to jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
index 0edc528b176..3ba305d8da2 100644
--- a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/GzipHandlerTest.java
+++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/GzipHandlerTest.java
@@ -16,7 +16,7 @@
// ========================================================================
//
-package org.eclipse.jetty.embedded;
+package org.eclipse.jetty.servlet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -29,24 +29,19 @@ import java.io.PrintWriter;
import java.util.zip.GZIPInputStream;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpTester;
-import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.LocalConnector;
-import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.IO;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@RunWith(AdvancedRunner.class)
public class GzipHandlerTest
{
private static String __content =
@@ -63,6 +58,8 @@ public class GzipHandlerTest
"Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
"et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
+ private static String __icontent = "BEFORE"+__content+"AFTER";
+
private Server _server;
private LocalConnector _connector;
@@ -73,25 +70,51 @@ public class GzipHandlerTest
_connector = new LocalConnector(_server);
_server.addConnector(_connector);
- Handler testHandler = new AbstractHandler()
- {
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
- ServletException
- {
- PrintWriter writer = response.getWriter();
- writer.write(__content);
- writer.close();
-
- baseRequest.setHandled(true);
- }
- };
-
GzipHandler gzipHandler = new GzipHandler();
- gzipHandler.setHandler(testHandler);
+ ServletContextHandler context = new ServletContextHandler(gzipHandler,"/ctx");
+ ServletHandler servlets = context.getServletHandler();
+
_server.setHandler(gzipHandler);
+ gzipHandler.setHandler(context);
+ context.setHandler(servlets);
+ servlets.addServletWithMapping(TestServlet.class,"/content");
+ servlets.addServletWithMapping(ForwardServlet.class,"/forward");
+ servlets.addServletWithMapping(IncludeServlet.class,"/include");
+
_server.start();
}
+
+ public static class TestServlet extends HttpServlet
+ {
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
+ {
+ PrintWriter writer = response.getWriter();
+ writer.write(__content);
+ }
+ }
+
+ public static class ForwardServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ getServletContext().getRequestDispatcher("/content").forward(request,response);
+ }
+ }
+
+ public static class IncludeServlet extends HttpServlet
+ {
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+ {
+ response.getWriter().write("BEFORE");
+ getServletContext().getRequestDispatcher("/content").include(request,response);
+ response.getWriter().write("AFTER");
+ }
+ }
@After
public void destroy() throws Exception
@@ -111,7 +134,7 @@ public class GzipHandlerTest
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setHeader("accept-encoding","gzip");
- request.setURI("/");
+ request.setURI("/ctx/content");
response = HttpTester.parseResponse(_connector.getResponses(request.generate()));
@@ -125,4 +148,54 @@ public class GzipHandlerTest
assertEquals(__content, testOut.toString("UTF8"));
}
+
+ @Test
+ public void testForwardGzipHandler() throws Exception
+ {
+ // generated and parsed test
+ HttpTester.Request request = HttpTester.newRequest();
+ HttpTester.Response response;
+
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.0");
+ request.setHeader("Host","tester");
+ request.setHeader("accept-encoding","gzip");
+ request.setURI("/ctx/forward");
+
+ response = HttpTester.parseResponse(_connector.getResponses(request.generate()));
+
+ assertTrue(response.get("Content-Encoding").equalsIgnoreCase("gzip"));
+ assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+ InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
+ ByteArrayOutputStream testOut = new ByteArrayOutputStream();
+ IO.copy(testIn,testOut);
+
+ assertEquals(__content, testOut.toString("UTF8"));
+ }
+
+ @Test
+ public void testIncludeGzipHandler() throws Exception
+ {
+ // generated and parsed test
+ HttpTester.Request request = HttpTester.newRequest();
+ HttpTester.Response response;
+
+ request.setMethod("GET");
+ request.setVersion("HTTP/1.0");
+ request.setHeader("Host","tester");
+ request.setHeader("accept-encoding","gzip");
+ request.setURI("/ctx/include");
+
+ response = HttpTester.parseResponse(_connector.getResponses(request.generate()));
+
+ assertTrue(response.get("Content-Encoding").equalsIgnoreCase("gzip"));
+ assertEquals(HttpServletResponse.SC_OK,response.getStatus());
+
+ InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
+ ByteArrayOutputStream testOut = new ByteArrayOutputStream();
+ IO.copy(testIn,testOut);
+
+ assertEquals(__icontent, testOut.toString("UTF8"));
+ }
}
diff --git a/jetty-servlet/src/test/resources/jetty-logging.properties b/jetty-servlet/src/test/resources/jetty-logging.properties
index 50696d67ec0..37f092141fc 100644
--- a/jetty-servlet/src/test/resources/jetty-logging.properties
+++ b/jetty-servlet/src/test/resources/jetty-logging.properties
@@ -1,5 +1,8 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=INFO
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.servlet.LEVEL=DEBUG
#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
+#org.eclipse.jetty.server.DebugListener.LEVEL=DEBUG
+#org.eclipse.jetty.server.HttpChannelState.LEVEL=DEBUG
\ No newline at end of file
diff --git a/jetty-servlets/pom.xml b/jetty-servlets/pom.xml
index 96970846068..00dada9234f 100644
--- a/jetty-servlets/pom.xml
+++ b/jetty-servlets/pom.xml
@@ -3,7 +3,7 @@
jetty-projectorg.eclipse.jetty
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-servlets
diff --git a/jetty-servlets/src/main/config/modules/servlets.mod b/jetty-servlets/src/main/config/modules/servlets.mod
index 2e977c05f14..5e1c84fc272 100644
--- a/jetty-servlets/src/main/config/modules/servlets.mod
+++ b/jetty-servlets/src/main/config/modules/servlets.mod
@@ -1,7 +1,8 @@
-#
-# Jetty Servlets Module
-#
-
+[description]
+Puts a collection of jetty utility servlets and filters
+on the server classpath (CGI, CrossOriginFilter, DosFilter,
+MultiPartFilter, PushCacheFilter, QoSFilter, etc.) for
+use by all webapplications.
[depend]
servlet
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
index fad67d27214..8a133828653 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -427,25 +427,31 @@ public class DoSFilter implements Filter
{
if (accepted)
{
- // Wake up the next highest priority request.
- for (int p = _queues.length - 1; p >= 0; --p)
+ try
{
- AsyncContext asyncContext = _queues[p].poll();
- if (asyncContext != null)
+ // Wake up the next highest priority request.
+ for (int p = _queues.length - 1; p >= 0; --p)
{
- ServletRequest candidate = asyncContext.getRequest();
- Boolean suspended = (Boolean)candidate.getAttribute(_suspended);
- if (suspended == Boolean.TRUE)
+ AsyncContext asyncContext = _queues[p].poll();
+ if (asyncContext != null)
{
- if (LOG.isDebugEnabled())
- LOG.debug("Resuming {}", request);
- candidate.setAttribute(_resumed, Boolean.TRUE);
- asyncContext.dispatch();
- break;
+ ServletRequest candidate = asyncContext.getRequest();
+ Boolean suspended = (Boolean)candidate.getAttribute(_suspended);
+ if (suspended == Boolean.TRUE)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Resuming {}", request);
+ candidate.setAttribute(_resumed, Boolean.TRUE);
+ asyncContext.dispatch();
+ break;
+ }
}
}
}
- _passes.release();
+ finally
+ {
+ _passes.release();
+ }
}
}
}
diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java
index d6710d74dbf..544c392a9cd 100644
--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java
+++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java
@@ -34,7 +34,6 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
-import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@@ -45,7 +44,7 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.PushBuilder;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
@@ -157,6 +156,9 @@ public class PushCacheFilter implements Filter
LOG.debug("{} {} referrer={} conditional={}", request.getMethod(), request.getRequestURI(), referrer, conditional);
String path = URIUtil.addPaths(request.getServletPath(), request.getPathInfo());
+ String query = request.getQueryString();
+ if (query != null)
+ path += "?" + query;
if (referrer != null)
{
HttpURI referrerURI = new HttpURI(referrer);
@@ -186,14 +188,13 @@ public class PushCacheFilter implements Filter
long primaryTimestamp = primaryResource._timestamp.get();
if (primaryTimestamp != 0)
{
- RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher(path);
if (now - primaryTimestamp < TimeUnit.MILLISECONDS.toNanos(_associatePeriod))
{
- ConcurrentMap associated = primaryResource._associated;
+ ConcurrentMap associated = primaryResource._associated;
// Not strictly concurrent-safe, just best effort to limit associations.
if (associated.size() <= _maxAssociations)
{
- if (associated.putIfAbsent(path, dispatcher) == null)
+ if (associated.putIfAbsent(path, path) == null)
{
if (LOG.isDebugEnabled())
LOG.debug("Associated {} to {}", path, referrerPathNoContext);
@@ -253,11 +254,14 @@ public class PushCacheFilter implements Filter
// Push associated for non conditional
if (!conditional && !primaryResource._associated.isEmpty())
{
- for (RequestDispatcher dispatcher : primaryResource._associated.values())
+ PushBuilder builder = Request.getBaseRequest(request).getPushBuilder();
+
+ for (String associated : primaryResource._associated.values())
{
if (LOG.isDebugEnabled())
- LOG.debug("Pushing {} for {}", dispatcher, path);
- ((Dispatcher)dispatcher).push(request);
+ LOG.debug("Pushing {} for {}", associated, path);
+
+ builder.path(associated).push();
}
}
@@ -297,7 +301,7 @@ public class PushCacheFilter implements Filter
private static class PrimaryResource
{
- private final ConcurrentMap _associated = new ConcurrentHashMap<>();
+ private final ConcurrentMap _associated = new ConcurrentHashMap<>();
private final AtomicLong _timestamp = new AtomicLong();
}
}
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/server/handler/gzip/GzipDefaultTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/server/handler/gzip/GzipDefaultTest.java
index 96dbf03cbce..78d5483961c 100644
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/server/handler/gzip/GzipDefaultTest.java
+++ b/jetty-servlets/src/test/java/org/eclipse/jetty/server/handler/gzip/GzipDefaultTest.java
@@ -38,6 +38,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.http.GzipHttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
@@ -147,7 +148,7 @@ public class GzipDefaultTest
//A HEAD request should have similar headers, but no body
response = tester.executeRequest("HEAD","/context/file.txt",5,TimeUnit.SECONDS);
assertThat("Response status",response.getStatus(),is(HttpStatus.OK_200));
- assertThat("ETag", response.get("ETag"), containsString(GzipHandler.ETAG_GZIP));
+ assertThat("ETag", response.get("ETag"), containsString(GzipHttpContent.ETAG_GZIP));
assertThat("Content encoding", response.get("Content-Encoding"), containsString("gzip"));
assertNull("Content length", response.get("Content-Length"));
diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DefaultServletStarvationTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DefaultServletStarvationTest.java
deleted file mode 100644
index 64362760714..00000000000
--- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DefaultServletStarvationTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.servlets;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.Socket;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.SocketChannel;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jetty.io.ManagedSelector;
-import org.eclipse.jetty.io.SelectChannelEndPoint;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.DefaultServlet;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
-import org.eclipse.jetty.toolchain.test.TestTracker;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class DefaultServletStarvationTest
-{
- @Rule
- public TestTracker tracker = new TestTracker();
- private Server _server;
-
- @After
- public void dispose() throws Exception
- {
- if (_server != null)
- _server.stop();
- }
-
- @Test
- public void testDefaultServletStarvation() throws Exception
- {
- int maxThreads = 2;
- QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, maxThreads);
- threadPool.setDetailedDump(true);
- _server = new Server(threadPool);
-
- // Prepare a big file to download.
- File directory = MavenTestingUtils.getTargetTestingDir();
- Files.createDirectories(directory.toPath());
- String resourceName = "resource.bin";
- Path resourcePath = Paths.get(directory.getPath(), resourceName);
- try (OutputStream output = Files.newOutputStream(resourcePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE))
- {
- byte[] chunk = new byte[1024];
- Arrays.fill(chunk,(byte)'X');
- chunk[chunk.length-2]='\r';
- chunk[chunk.length-1]='\n';
- for (int i = 0; i < 256 * 1024; ++i)
- output.write(chunk);
- }
-
- final CountDownLatch writePending = new CountDownLatch(1);
- ServerConnector connector = new ServerConnector(_server, 0, 1)
- {
- @Override
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
- {
- return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout())
- {
- @Override
- protected void onIncompleteFlush()
- {
- super.onIncompleteFlush();
- writePending.countDown();
- }
- };
- }
- };
- _server.addConnector(connector);
-
- ServletContextHandler context = new ServletContextHandler(_server, "/");
- context.setResourceBase(directory.toURI().toString());
- context.addServlet(DefaultServlet.class, "/*").setAsyncSupported(false);
- _server.setHandler(context);
-
- _server.start();
-
- List sockets = new ArrayList<>();
- for (int i = 0; i < maxThreads; ++i)
- {
- Socket socket = new Socket("localhost", connector.getLocalPort());
- sockets.add(socket);
- OutputStream output = socket.getOutputStream();
- String request = "" +
- "GET /" + resourceName + " HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
-// "Connection: close\r\n" +
- "\r\n";
- output.write(request.getBytes(StandardCharsets.UTF_8));
- output.flush();
- Thread.sleep(100);
- }
-
-
- // Wait for a the servlet to block.
- Assert.assertTrue(writePending.await(5, TimeUnit.SECONDS));
-
- Thread.sleep(1000);
- _server.dumpStdErr();
- Thread.sleep(1000);
-
-
- ScheduledFuture> dumper = Executors.newSingleThreadScheduledExecutor().schedule(new Runnable()
- {
- @Override
- public void run()
- {
- _server.dumpStdErr();
- }
- }, 10, TimeUnit.SECONDS);
-
-
- long expected = Files.size(resourcePath);
- byte[] buffer = new byte[48 * 1024];
- for (Socket socket : sockets)
- {
- String socketString = socket.toString();
- System.out.println("Reading socket " + socketString+"...");
- long total = 0;
- InputStream input = socket.getInputStream();
-
- // look for CRLFCRLF
- StringBuilder header = new StringBuilder();
- int state=0;
- while (state<4 && header.length()<2048)
- {
- int ch=input.read();
- if (ch<0)
- break;
- header.append((char)ch);
- switch(state)
- {
- case 0:
- if (ch=='\r')
- state=1;
- break;
- case 1:
- if (ch=='\n')
- state=2;
- else
- state=0;
- break;
- case 2:
- if (ch=='\r')
- state=3;
- else
- state=0;
- break;
- case 3:
- if (ch=='\n')
- state=4;
- else
- state=0;
- break;
- }
- }
- System.out.println("Header socket " + socketString+"\n"+header.toString());
-
- while (total sockets = new ArrayList<>();
+ for (int i = 0; i < maxThreads*2; ++i)
+ {
+ Socket socket = new Socket("localhost", connector.getLocalPort());
+ sockets.add(socket);
+ OutputStream output = socket.getOutputStream();
+ String request = "" +
+ "GET /" + resourceName + " HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n";
+ output.write(request.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+ Thread.sleep(100);
+ }
+
+ // Wait for a the servlet to block.
+ Assert.assertTrue(writePending.await(5, TimeUnit.SECONDS));
+
+ long expected = Files.size(resourcePath);
+ byte[] buffer = new byte[48 * 1024];
+ List> totals = new ArrayList<>();
+ for (Socket socket : sockets)
+ {
+ final Exchanger x = new Exchanger<>();
+ totals.add(x);
+ final InputStream input = socket.getInputStream();
+
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ long total=0;
+ try
+ {
+ // look for CRLFCRLF
+ StringBuilder header = new StringBuilder();
+ int state=0;
+ while (state<4 && header.length()<2048)
+ {
+ int ch=input.read();
+ if (ch<0)
+ break;
+ header.append((char)ch);
+ switch(state)
+ {
+ case 0:
+ if (ch=='\r')
+ state=1;
+ break;
+ case 1:
+ if (ch=='\n')
+ state=2;
+ else
+ state=0;
+ break;
+ case 2:
+ if (ch=='\r')
+ state=3;
+ else
+ state=0;
+ break;
+ case 3:
+ if (ch=='\n')
+ state=4;
+ else
+ state=0;
+ break;
+ }
+ }
+
+ while (total x : totals)
+ {
+ Long total = x.exchange(-1L,10000,TimeUnit.SECONDS);
+ Assert.assertEquals(expected,total.longValue());
+ }
+
+ // We could read everything, good.
+ for (Socket socket : sockets)
+ socket.close();
+ }
+
+ @Test
+ public void testFailureStarvation() throws Exception
+ {
+ try
+ {
+ ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(true);
+
+ int acceptors = 0;
+ int selectors = 1;
+ int maxThreads = 10;
+ final int barried=maxThreads-acceptors-selectors;
+ final CyclicBarrier barrier = new CyclicBarrier(barried);
+
+
+ QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, maxThreads);
+ threadPool.setDetailedDump(true);
+ _server = new Server(threadPool);
+
+
+ ServerConnector connector = new ServerConnector(_server, acceptors, selectors)
+ {
+ @Override
+ protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
+ {
+ return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout())
+ {
+
+ @Override
+ public boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ super.flush(buffers[0]);
+ throw new IOException("TEST FAILURE");
+ }
+
+ };
+ }
+ };
+ connector.setIdleTimeout(Long.MAX_VALUE);
+ _server.addConnector(connector);
+
+ final AtomicInteger count = new AtomicInteger(0);
+ _server.setHandler(new AbstractHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ {
+ int c=count.getAndIncrement();
+ try
+ {
+ if (c sockets = new ArrayList<>();
+ for (int i = 0; i < maxThreads*2; ++i)
+ {
+ Socket socket = new Socket("localhost", connector.getLocalPort());
+ sockets.add(socket);
+ OutputStream output = socket.getOutputStream();
+ String request = "" +
+ "GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ // "Connection: close\r\n" +
+ "\r\n";
+ output.write(request.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+ }
+
+
+ byte[] buffer = new byte[48 * 1024];
+ List> totals = new ArrayList<>();
+ for (Socket socket : sockets)
+ {
+ final Exchanger x = new Exchanger<>();
+ totals.add(x);
+ final InputStream input = socket.getInputStream();
+
+ new Thread()
+ {
+ @Override
+ public void run()
+ {
+ int read=0;
+ try
+ {
+ // look for CRLFCRLF
+ StringBuilder header = new StringBuilder();
+ int state=0;
+ while (state<4 && header.length()<2048)
+ {
+ int ch=input.read();
+ if (ch<0)
+ break;
+ header.append((char)ch);
+ switch(state)
+ {
+ case 0:
+ if (ch=='\r')
+ state=1;
+ break;
+ case 1:
+ if (ch=='\n')
+ state=2;
+ else
+ state=0;
+ break;
+ case 2:
+ if (ch=='\r')
+ state=3;
+ else
+ state=0;
+ break;
+ case 3:
+ if (ch=='\n')
+ state=4;
+ else
+ state=0;
+ break;
+ }
+ }
+
+ read=input.read(buffer);
+ }
+ catch (IOException e)
+ {
+ // e.printStackTrace();
+ }
+ finally
+ {
+ try
+ {
+ x.exchange(read);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }.start();
+ }
+
+ for (Exchanger x : totals)
+ {
+ Integer read = x.exchange(-1,10,TimeUnit.SECONDS);
+ Assert.assertEquals(-1,read.intValue());
+ }
+
+ // We could read everything, good.
+ for (Socket socket : sockets)
+ socket.close();
+
+ _server.stop();
+ }
+ finally
+ {
+ ((StdErrLog)Log.getLogger(HttpChannel.class)).setHideStacks(false);
+ }
+ }
+}
diff --git a/jetty-spring/pom.xml b/jetty-spring/pom.xml
index 681e37c2b91..4afb769e561 100644
--- a/jetty-spring/pom.xml
+++ b/jetty-spring/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-spring
diff --git a/jetty-spring/src/main/config/modules/spring.mod b/jetty-spring/src/main/config/modules/spring.mod
index 39b9b8d85a3..f6419b791b3 100644
--- a/jetty-spring/src/main/config/modules/spring.mod
+++ b/jetty-spring/src/main/config/modules/spring.mod
@@ -1,6 +1,6 @@
-#
-# Spring
-#
+[description]
+Enable spring configuration processing so all jetty style
+xml files can optionally be written as spring beans
[name]
spring
diff --git a/jetty-start/dependency-reduced-pom.xml b/jetty-start/dependency-reduced-pom.xml
new file mode 100644
index 00000000000..e6b0df4c954
--- /dev/null
+++ b/jetty-start/dependency-reduced-pom.xml
@@ -0,0 +1,83 @@
+
+
+
+ jetty-project
+ org.eclipse.jetty
+ 9.4.0-SNAPSHOT
+
+ 4.0.0
+ jetty-start
+ Jetty :: Start
+ The start utility
+ http://www.eclipse.org/jetty
+
+
+
+ maven-jar-plugin
+
+
+
+ org.eclipse.jetty.start.Main
+
+
+
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
+ org.eclipse.jetty.start.*
+
+
+
+ maven-shade-plugin
+ 2.4
+
+
+ package
+
+ shade
+
+
+
+
+ true
+
+
+ org.eclipse.jetty:jetty-util
+
+
+
+
+ org.eclipse.jetty.util
+ org.eclipse.jetty.start.util
+
+
+
+
+
+
+
+
+ org.eclipse.jetty.toolchain
+ jetty-test-helper
+ 3.1
+ test
+
+
+ junit
+ junit
+
+
+ hamcrest-library
+ org.hamcrest
+
+
+
+
+
+ ${project.groupId}.start
+ start.jar
+
+
+
diff --git a/jetty-start/pom.xml b/jetty-start/pom.xml
index 57cf112b399..b406b047252 100644
--- a/jetty-start/pom.xml
+++ b/jetty-start/pom.xml
@@ -2,7 +2,7 @@
org.eclipse.jettyjetty-project
- 9.3.4-SNAPSHOT
+ 9.4.0-SNAPSHOT4.0.0jetty-start
@@ -32,9 +32,41 @@
org.eclipse.jetty.start.*
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4
+
+ true
+
+
+ org.eclipse.jetty:jetty-util
+
+
+
+
+ org.eclipse.jetty.util
+ org.eclipse.jetty.start.util
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${project.version}
+ org.eclipse.jetty.toolchainjetty-test-helper
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java
index 46b312223a6..8af45e7ae06 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseBuilder.java
@@ -23,17 +23,20 @@ import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import javax.management.RuntimeErrorException;
import org.eclipse.jetty.start.builders.StartDirBuilder;
import org.eclipse.jetty.start.builders.StartIniBuilder;
import org.eclipse.jetty.start.fileinits.MavenLocalRepoFileInitializer;
import org.eclipse.jetty.start.fileinits.TestFileInitializer;
import org.eclipse.jetty.start.fileinits.UriFileInitializer;
-import org.eclipse.jetty.start.graph.CriteriaSetPredicate;
-import org.eclipse.jetty.start.graph.UniqueCriteriaPredicate;
-import org.eclipse.jetty.start.graph.Predicate;
-import org.eclipse.jetty.start.graph.Selection;
/**
* Build a start configuration in ${jetty.base}, including
@@ -94,37 +97,6 @@ public class BaseBuilder
}
}
- private void ackLicenses() throws IOException
- {
- if (startArgs.isLicenseCheckRequired())
- {
- if (startArgs.isApproveAllLicenses())
- {
- StartLog.info("All Licenses Approved via Command Line Option");
- }
- else
- {
- Licensing licensing = new Licensing();
- for (Module module : startArgs.getAllModules().getSelected())
- {
- if (!module.hasFiles(baseHome,startArgs.getProperties()))
- {
- licensing.addModule(module);
- }
- }
-
- if (licensing.hasLicenses())
- {
- StartLog.debug("Requesting License Acknowledgement");
- if (!licensing.acknowledgeLicenses())
- {
- StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
- System.exit(1);
- }
- }
- }
- }
- }
/**
* Build out the Base directory (if needed)
@@ -135,112 +107,101 @@ public class BaseBuilder
public boolean build() throws IOException
{
Modules modules = startArgs.getAllModules();
- boolean dirty = false;
- String dirCriteria = "";
- String iniCriteria = "";
- Selection startDirSelection = new Selection(dirCriteria);
- Selection startIniSelection = new Selection(iniCriteria);
-
- List startDNames = new ArrayList<>();
- startDNames.addAll(startArgs.getAddToStartdIni());
- List startIniNames = new ArrayList<>();
- startIniNames.addAll(startArgs.getAddToStartIni());
-
- int count = 0;
- count += modules.selectNodes(startDNames,startDirSelection);
- count += modules.selectNodes(startIniNames,startIniSelection);
-
- // look for ambiguous declaration found in both places
- Predicate ambiguousPredicate = new CriteriaSetPredicate(dirCriteria,iniCriteria);
- List ambiguous = modules.getMatching(ambiguousPredicate);
-
- if (ambiguous.size() > 0)
+ // Select all the added modules to determine which ones are newly enabled
+ Set enabled = new HashSet<>();
+ Set startDModules = new HashSet<>();
+ Set startModules = new HashSet<>();
+ if (!startArgs.getAddToStartdIni().isEmpty() || !startArgs.getAddToStartIni().isEmpty())
{
- StringBuilder warn = new StringBuilder();
- warn.append("Ambiguous module locations detected, defaulting to --add-to-start for the following module selections:");
- warn.append(" [");
-
- for (int i = 0; i < ambiguous.size(); i++)
+ if (startArgs.isAddToStartdFirst())
{
- if (i > 0)
- {
- warn.append(", ");
- }
- warn.append(ambiguous.get(i).getName());
+ for (String name:startArgs.getAddToStartdIni())
+ startDModules.addAll(modules.select(name,"--add-to-startd"));
+ for (String name:startArgs.getAddToStartIni())
+ startModules.addAll(modules.select(name,"--add-to-start"));
}
- warn.append(']');
- StartLog.warn(warn.toString());
+ else
+ {
+ for (String name:startArgs.getAddToStartIni())
+ startModules.addAll(modules.select(name,"--add-to-start"));
+ for (String name:startArgs.getAddToStartdIni())
+ startDModules.addAll(modules.select(name,"--add-to-startd"));
+ }
+ enabled.addAll(startDModules);
+ enabled.addAll(startModules);
}
- StartLog.debug("Adding %s new module(s)",count);
+ if (StartLog.isDebugEnabled())
+ StartLog.debug("startD=%s start=%s",startDModules,startModules);
- // Acknowledge Licenses
- ackLicenses();
-
- // Collect specific modules to enable
- // Should match 'criteria', with no other selections.explicit
- Predicate startDMatcher = new UniqueCriteriaPredicate(dirCriteria);
- Predicate startIniMatcher = new UniqueCriteriaPredicate(iniCriteria);
-
- List startDModules = modules.getMatching(startDMatcher);
- List startIniModules = modules.getMatching(startIniMatcher);
+ // Check the licenses
+ if (startArgs.isLicenseCheckRequired())
+ {
+ Licensing licensing = new Licensing();
+ for (String name : enabled)
+ licensing.addModule(modules.get(name));
+
+ if (licensing.hasLicenses())
+ {
+ if (startArgs.isApproveAllLicenses())
+ {
+ StartLog.info("All Licenses Approved via Command Line Option");
+ }
+ else if (!licensing.acknowledgeLicenses())
+ {
+ StartLog.warn(EXITING_LICENSE_NOT_ACKNOWLEDGED);
+ System.exit(1);
+ }
+ }
+ }
+ // generate the files
List files = new ArrayList();
-
+ AtomicReference builder = new AtomicReference<>();
+ AtomicBoolean modified = new AtomicBoolean();
+ Consumer do_build_add = module ->
+ {
+ try
+ {
+ if (module.isSkipFilesValidation())
+ {
+ StartLog.debug("Skipping [files] validation on %s",module.getName());
+ }
+ else
+ {
+ if (builder.get().addModule(module))
+ modified.set(true);
+ for (String file : module.getFiles())
+ files.add(new FileArg(module,startArgs.getProperties().expand(file)));
+ }
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ };
+
if (!startDModules.isEmpty())
{
- StartDirBuilder builder = new StartDirBuilder(this);
- for (Module mod : startDModules)
- {
- if (ambiguous.contains(mod))
- {
- // skip ambiguous module
- continue;
- }
-
- if (mod.isSkipFilesValidation())
- {
- StartLog.debug("Skipping [files] validation on %s",mod.getName());
- }
- else
- {
- dirty |= builder.addModule(mod);
- for (String file : mod.getFiles())
- {
- files.add(new FileArg(mod,startArgs.getProperties().expand(file)));
- }
- }
- }
+ builder.set(new StartDirBuilder(this));
+ startDModules.stream().map(n->modules.get(n)).forEach(do_build_add);
}
- if (!startIniModules.isEmpty())
+ if (!startModules.isEmpty())
{
- StartIniBuilder builder = new StartIniBuilder(this);
- for (Module mod : startIniModules)
- {
- if (mod.isSkipFilesValidation())
- {
- StartLog.debug("Skipping [files] validation on %s",mod.getName());
- }
- else
- {
- dirty |= builder.addModule(mod);
- for (String file : mod.getFiles())
- {
- files.add(new FileArg(mod,startArgs.getProperties().expand(file)));
- }
- }
- }
+ builder.set(new StartIniBuilder(this));
+ startModules.stream().map(n->modules.get(n)).forEach(do_build_add);
}
-
- // Process files
+
files.addAll(startArgs.getFiles());
- dirty |= processFileResources(files);
-
- return dirty;
+ if (!files.isEmpty() && processFileResources(files))
+ modified.set(Boolean.TRUE);
+
+ return modified.get();
}
-
+
+
public BaseHome getBaseHome()
{
return baseHome;
@@ -273,7 +234,7 @@ public class BaseBuilder
}
// make the directories in ${jetty.base} that we need
- FS.ensureDirectoryExists(file.getParent());
+ boolean modified = FS.ensureDirectoryExists(file.getParent());
URI uri = URI.create(arg.uri);
@@ -332,7 +293,7 @@ public class BaseBuilder
if (startArgs.isTestingModeEnabled())
{
StartLog.log("TESTING MODE","Skipping required file check on: %s",shortRef);
- return true;
+ return false;
}
StartLog.warn("Missing Required File: %s",baseHome.toShortForm(file));
@@ -343,7 +304,7 @@ public class BaseBuilder
StartLog.warn(" Run start.jar --create-files to download");
}
- return true;
+ return false;
}
}
}
@@ -372,7 +333,8 @@ public class BaseBuilder
Path file = baseHome.getBasePath(arg.location);
try
{
- dirty |= processFileResource(arg,file);
+ boolean processed = processFileResource(arg,file);
+ dirty |= processed;
}
catch (Throwable t)
{
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Licensing.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Licensing.java
index eb5149bc81d..48bdedbb5ef 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Licensing.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Licensing.java
@@ -57,6 +57,8 @@ public class Licensing
public boolean acknowledgeLicenses() throws IOException
{
+ StartLog.debug("Requesting License Acknowledgement");
+
if (!hasLicenses())
{
return true;
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 192affe0a98..fec8b98a620 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
@@ -41,8 +41,6 @@ import java.util.List;
import java.util.Locale;
import org.eclipse.jetty.start.config.CommandLineConfigSource;
-import org.eclipse.jetty.start.graph.GraphException;
-import org.eclipse.jetty.start.graph.Selection;
/**
* Main start class.
@@ -284,59 +282,60 @@ public class Main
StartArgs args = new StartArgs();
args.parse(baseHome.getConfigSources());
+ // ------------------------------------------------------------
+ // 3) Module Registration
+ Modules modules = new Modules(baseHome,args);
+ StartLog.debug("Registering all modules");
+ modules.registerAll();
+
+ // ------------------------------------------------------------
+ // 4) Active Module Resolution
+ for (String enabledModule : args.getEnabledModules())
+ {
+ for (String source : args.getSources(enabledModule))
+ {
+ String shortForm = baseHome.toShortForm(source);
+ modules.select(enabledModule,shortForm);
+ }
+ }
+
+ StartLog.debug("Sorting Modules");
try
{
- // ------------------------------------------------------------
- // 3) Module Registration
- Modules modules = new Modules(baseHome,args);
- StartLog.debug("Registering all modules");
- modules.registerAll();
-
- // ------------------------------------------------------------
- // 4) Active Module Resolution
- for (String enabledModule : args.getEnabledModules())
- {
- for (String source : args.getSources(enabledModule))
- {
- String shortForm = baseHome.toShortForm(source);
- modules.selectNode(enabledModule,new Selection(shortForm));
- }
- }
-
- StartLog.debug("Building Module Graph");
- modules.buildGraph();
-
- args.setAllModules(modules);
- List activeModules = modules.getSelected();
-
- final Version START_VERSION = new Version(StartArgs.VERSION);
-
- for(Module enabled: activeModules)
- {
- if(enabled.getVersion().isNewerThan(START_VERSION))
- {
- throw new UsageException(UsageException.ERR_BAD_GRAPH, "Module [" + enabled.getName() + "] specifies jetty version [" + enabled.getVersion()
- + "] which is newer than this version of jetty [" + START_VERSION + "]");
- }
- }
-
- for(String name: args.getSkipFileValidationModules())
- {
- Module module = modules.get(name);
- module.setSkipFilesValidation(true);
- }
-
- // ------------------------------------------------------------
- // 5) Lib & XML Expansion / Resolution
- args.expandLibs(baseHome);
- args.expandModules(baseHome,activeModules);
-
+ modules.sort();
}
- catch (GraphException e)
+ catch (Exception e)
{
throw new UsageException(ERR_BAD_GRAPH,e);
}
+ args.setAllModules(modules);
+ List activeModules = modules.getSelected();
+
+
+ final Version START_VERSION = new Version(StartArgs.VERSION);
+
+ for(Module enabled: activeModules)
+ {
+ if(enabled.getVersion().isNewerThan(START_VERSION))
+ {
+ throw new UsageException(UsageException.ERR_BAD_GRAPH, "Module [" + enabled.getName() + "] specifies jetty version [" + enabled.getVersion()
+ + "] which is newer than this version of jetty [" + START_VERSION + "]");
+ }
+ }
+
+ for(String name: args.getSkipFileValidationModules())
+ {
+ Module module = modules.get(name);
+ module.setSkipFilesValidation(true);
+ }
+
+ // ------------------------------------------------------------
+ // 5) Lib & XML Expansion / Resolution
+ args.expandLibs(baseHome);
+ args.expandModules(baseHome,activeModules);
+
+
// ------------------------------------------------------------
// 6) Resolve Extra XMLs
args.resolveExtraXmls(baseHome);
@@ -403,13 +402,12 @@ public class Main
doStop(args);
}
+ // Check base directory
BaseBuilder baseBuilder = new BaseBuilder(baseHome,args);
if(baseBuilder.build())
- {
- // base directory changed.
StartLog.info("Base directory was modified");
- return;
- }
+ else if (args.isDownload() || !args.getAddToStartdIni().isEmpty() || !args.getAddToStartIni().isEmpty())
+ StartLog.info("Base directory was not modified");
// Informational command line, don't run jetty
if (!args.isRun())
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 6d5ca285e8f..b20f56523a9 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
@@ -27,41 +27,38 @@ import java.nio.file.Path;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
+import java.util.Set;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
-import org.eclipse.jetty.start.graph.Node;
+import java.util.stream.Collectors;
/**
* Represents a Module metadata, as defined in Jetty.
*/
-public class Module extends Node
+public class Module
{
private static final String VERSION_UNSPECIFIED = "9.2";
- public static class NameComparator implements Comparator
- {
- private Collator collator = Collator.getInstance();
-
- @Override
- public int compare(Module o1, Module o2)
- {
- // by name (not really needed, but makes for predictable test cases)
- CollationKey k1 = collator.getCollationKey(o1.fileRef);
- CollationKey k2 = collator.getCollationKey(o2.fileRef);
- return k1.compareTo(k2);
- }
- }
-
- /** The file of the module */
- private Path file;
-
/** The name of this Module (as a filesystem reference) */
private String fileRef;
+ /** The file of the module */
+ private final Path file;
+
+ /** The name of the module */
+ private String name;
+
+ /** The module description */
+ private List description;
+
/** The version of Jetty the module supports */
private Version version;
@@ -70,17 +67,22 @@ public class Module extends Node
/** List of ini template lines */
private List iniTemplate;
- private boolean hasIniTemplate = false;
/** List of default config */
private List defaultConfig;
- private boolean hasDefaultConfig = false;
/** List of library options for this Module */
private List libs;
/** List of files for this Module */
private List files;
+
+ /** List of selections for this Module */
+ private Set selections;
+
+ /** Boolean true if directly enabled, false if selections are transitive */
+ private boolean enabled;
+
/** Skip File Validation (default: false) */
private boolean skipFilesValidation = false;
@@ -89,6 +91,12 @@ public class Module extends Node
/** License lines */
private List license;
+
+ /** Dependencies */
+ private Set depends;
+
+ /** Optional */
+ private Set optional;
public Module(BaseHome basehome, Path file) throws FileNotFoundException, IOException
{
@@ -97,12 +105,17 @@ public class Module extends Node
// Strip .mod
this.fileRef = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(file.getFileName().toString()).replaceFirst("");
- this.setName(fileRef);
+ name=fileRef;
init(basehome);
process(basehome);
}
+ public String getName()
+ {
+ return name;
+ }
+
@Override
public boolean equals(Object obj)
{
@@ -135,13 +148,9 @@ public class Module extends Node
public void expandProperties(Props props)
{
- // Expand Parents
- List parents = new ArrayList<>();
- for (String parent : getParentNames())
- {
- parents.add(props.expand(parent));
- }
- setParentNames(parents);
+ Function expander = d->{return props.expand(d);};
+ depends=depends.stream().map(expander).collect(Collectors.toSet());
+ optional=optional.stream().map(expander).collect(Collectors.toSet());
}
public List getDefaultConfig()
@@ -196,12 +205,12 @@ public class Module extends Node
public boolean hasDefaultConfig()
{
- return hasDefaultConfig;
+ return !defaultConfig.isEmpty();
}
public boolean hasIniTemplate()
{
- return hasIniTemplate;
+ return !iniTemplate.isEmpty();
}
@Override
@@ -220,6 +229,7 @@ public class Module extends Node
private void init(BaseHome basehome)
{
+ description = new ArrayList<>();
xmls = new ArrayList<>();
defaultConfig = new ArrayList<>();
iniTemplate = new ArrayList<>();
@@ -227,6 +237,9 @@ public class Module extends Node
files = new ArrayList<>();
jvmArgs = new ArrayList<>();
license = new ArrayList<>();
+ depends = new HashSet<>();
+ optional = new HashSet<>();
+ selections = new HashSet<>();
String name = basehome.toShortForm(file);
@@ -238,7 +251,7 @@ public class Module extends Node
throw new RuntimeException("Invalid Module location (must be located under /modules/ directory): " + name);
}
this.fileRef = mat.group(1).replace('\\','/');
- setName(this.fileRef);
+ this.name=this.fileRef;
}
/**
@@ -248,7 +261,7 @@ public class Module extends Node
*/
public boolean isDynamic()
{
- return !getName().equals(fileRef);
+ return !name.equals(fileRef);
}
public boolean hasFiles(BaseHome baseHome, Props props)
@@ -299,7 +312,6 @@ public class Module extends Node
if ("INI-TEMPLATE".equals(sectionType))
{
iniTemplate.add(line);
- hasIniTemplate = true;
}
}
else
@@ -309,8 +321,11 @@ public class Module extends Node
case "":
// ignore (this would be entries before first section)
break;
+ case "DESCRIPTION":
+ description.add(line);
+ break;
case "DEPEND":
- addParentName(line);
+ depends.add(line);
break;
case "FILES":
files.add(line);
@@ -318,11 +333,9 @@ public class Module extends Node
case "DEFAULTS": // old name introduced in 9.2.x
case "INI": // new name for 9.3+
defaultConfig.add(line);
- hasDefaultConfig = true;
break;
case "INI-TEMPLATE":
iniTemplate.add(line);
- hasIniTemplate = true;
break;
case "LIB":
libs.add(line);
@@ -332,10 +345,10 @@ public class Module extends Node
license.add(line);
break;
case "NAME":
- setName(line);
+ name=line;
break;
case "OPTIONAL":
- addOptionalParentName(line);
+ optional.add(line);
break;
case "EXEC":
jvmArgs.add(line);
@@ -387,7 +400,62 @@ public class Module extends Node
{
str.append(",selected");
}
+ if (isTransitive())
+ {
+ str.append(",transitive");
+ }
str.append(']');
return str.toString();
}
+
+ public Set getDepends()
+ {
+ return Collections.unmodifiableSet(depends);
+ }
+
+ public Set getOptional()
+ {
+ return Collections.unmodifiableSet(optional);
+ }
+
+ public List getDescription()
+ {
+ return description;
+ }
+
+ public boolean isSelected()
+ {
+ return !selections.isEmpty();
+ }
+
+ public Set getSelections()
+ {
+ return Collections.unmodifiableSet(selections);
+ }
+
+ public boolean addSelection(String enabledFrom,boolean transitive)
+ {
+ boolean updated=selections.isEmpty();
+ if (transitive)
+ {
+ if (!enabled)
+ selections.add(enabledFrom);
+ }
+ else
+ {
+ if (!enabled)
+ {
+ updated=true;
+ selections.clear(); // clear any transitive enabling
+ }
+ enabled=true;
+ selections.add(enabledFrom);
+ }
+ return updated;
+ }
+
+ public boolean isTransitive()
+ {
+ return isSelected() && !enabled;
+ }
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
index c292a173193..347ee1f4bad 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java
@@ -25,13 +25,8 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
-import java.util.Collection;
import java.util.List;
-import org.eclipse.jetty.start.graph.Graph;
-import org.eclipse.jetty.start.graph.Node;
-import org.eclipse.jetty.start.graph.Selection;
-
/**
* Generate a graphviz dot graph of the modules found
*/
@@ -186,7 +181,7 @@ public class ModuleGraphWriter
if (module.isSelected())
{
writeModuleDetailHeader(out,"ENABLED");
- for (Selection selection : module.getSelections())
+ for (String selection : module.getSelections())
{
writeModuleDetailLine(out,"via: " + selection);
}
@@ -233,32 +228,21 @@ public class ModuleGraphWriter
out.println(" node [ labeljust = l ];");
- for (int depth = 0; depth <= allmodules.getMaxDepth(); depth++)
+ for (Module module: allmodules)
{
- out.println();
- Collection depthModules = allmodules.getModulesAtDepth(depth);
- if (depthModules.size() > 0)
- {
- out.printf(" /* Level %d */%n",depth);
- out.println(" { rank = same;");
- for (Module module : depthModules)
- {
- boolean resolved = enabled.contains(module);
- writeModuleNode(out,module,resolved);
- }
- out.println(" }");
- }
+ boolean resolved = enabled.contains(module);
+ writeModuleNode(out,module,resolved);
}
}
- private void writeRelationships(PrintWriter out, Graph modules, List enabled)
+ private void writeRelationships(PrintWriter out, Iterable modules, List enabled)
{
for (Module module : modules)
{
- for (Node> parent : module.getParentEdges())
- {
- out.printf(" \"%s\" -> \"%s\";%n",module.getName(),parent.getName());
- }
+ for (String depends : module.getDepends())
+ out.printf(" \"%s\" -> \"%s\";%n",module.getName(),depends);
+ for (String optional : module.getOptional())
+ out.printf(" \"%s\" => \"%s\";%n",module.getName(),optional);
}
}
}
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 68db15e627d..d9f26062421 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
@@ -22,18 +22,26 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
-import org.eclipse.jetty.start.graph.Graph;
-import org.eclipse.jetty.start.graph.GraphException;
-import org.eclipse.jetty.start.graph.OnlyTransitivePredicate;
-import org.eclipse.jetty.start.graph.Selection;
+import org.eclipse.jetty.util.TopologicalSort;
/**
* Access for all modules declared, as well as what is enabled.
*/
-public class Modules extends Graph
+public class Modules implements Iterable
{
+ private final List modules = new ArrayList<>();
+ private final Map names = new HashMap<>();
private final BaseHome baseHome;
private final StartArgs args;
@@ -41,8 +49,6 @@ public class Modules extends Graph
{
this.baseHome = basehome;
this.args = args;
- this.setSelectionTerm("enable");
- this.setNodeTerm("module");
String java_version = System.getProperty("java.version");
if (java_version!=null)
@@ -53,24 +59,16 @@ public class Modules extends Graph
public void dump()
{
- List ordered = new ArrayList<>();
- ordered.addAll(getNodes());
- Collections.sort(ordered,new Module.NameComparator());
-
- List active = getSelected();
-
- for (Module module : ordered)
+ List ordered = modules.stream().map(m->{return m.getName();}).collect(Collectors.toList());
+ Collections.sort(ordered);
+ ordered.stream().map(n->{return get(n);}).forEach(module->
{
- boolean activated = active.contains(module);
- boolean selected = module.isSelected();
- boolean transitive = selected && module.matches(OnlyTransitivePredicate.INSTANCE);
-
String status = "[ ]";
- if (transitive)
+ if (module.isTransitive())
{
status = "[t]";
}
- else if (selected)
+ else if (module.isSelected())
{
status = "[x]";
}
@@ -80,10 +78,18 @@ public class Modules extends Graph
{
System.out.printf(" Ref: %s%n",module.getFilesystemRef());
}
- for (String parent : module.getParentNames())
+ for (String description : module.getDescription())
+ {
+ System.out.printf(" : %s%n",description);
+ }
+ for (String parent : module.getDepends())
{
System.out.printf(" Depend: %s%n",parent);
}
+ for (String optional : module.getOptional())
+ {
+ System.out.printf(" Optional: %s%n",optional);
+ }
for (String lib : module.getLibs())
{
System.out.printf(" LIB: %s%n",lib);
@@ -92,91 +98,34 @@ public class Modules extends Graph
{
System.out.printf(" XML: %s%n",xml);
}
- if (StartLog.isDebugEnabled())
+ for (String jvm : module.getJvmArgs())
{
- System.out.printf(" depth: %d%n",module.getDepth());
+ System.out.printf(" JVM: %s%n",jvm);
}
- if (activated)
+ if (module.isSelected())
{
- for (Selection selection : module.getSelections())
+ for (String selection : module.getSelections())
{
- System.out.printf(" Enabled: %s%n",selection);
+ System.out.printf(" Enabled: %s%n",selection);
}
}
- else
- {
- System.out.printf(" Enabled: %n");
- }
- }
+ });
}
- @Override
- public Module resolveNode(String name)
+ public void dumpSelected()
{
- String expandedName = args.getProperties().expand(name);
-
- if (Props.hasPropertyKey(expandedName))
+ int i=0;
+ for (Module module:getSelected())
{
- StartLog.debug("Not yet able to expand property in: %s",name);
- return null;
- }
-
- Path file = baseHome.getPath("modules/" + expandedName + ".mod");
- if (FS.canReadFile(file))
- {
- Module parent = registerModule(file);
- parent.expandProperties(args.getProperties());
- updateParentReferencesTo(parent);
- return parent;
- }
- else
- {
- if (!Props.hasPropertyKey(name))
+ String name=module.getName();
+ String index=(i++)+")";
+ for (String s:module.getSelections())
{
- StartLog.debug("Missing module definition: [ Mod: %s | File: %s ]",name,file);
- }
- return null;
- }
- }
-
- @Override
- public void onNodeSelected(Module module)
- {
- StartLog.debug("on node selected: [%s] (%s.mod)",module.getName(),module.getFilesystemRef());
- args.parseModule(module);
- module.expandProperties(args.getProperties());
- }
-
- public List normalizeLibs(List active)
- {
- List libs = new ArrayList<>();
- for (Module module : active)
- {
- for (String lib : module.getLibs())
- {
- if (!libs.contains(lib))
- {
- libs.add(lib);
- }
+ System.out.printf(" %4s %-15s %s%n",index,name,s);
+ index="";
+ name="";
}
}
- return libs;
- }
-
- public List normalizeXmls(List active)
- {
- List xmls = new ArrayList<>();
- for (Module module : active)
- {
- for (String xml : module.getXmls())
- {
- if (!xmls.contains(xml))
- {
- xmls.add(xml);
- }
- }
- }
- return xmls;
}
public void registerAll() throws IOException
@@ -191,54 +140,26 @@ public class Modules extends Graph
{
if (!FS.canReadFile(file))
{
- throw new GraphException("Cannot read file: " + file);
+ throw new IllegalStateException("Cannot read file: " + file);
}
String shortName = baseHome.toShortForm(file);
try
{
StartLog.debug("Registering Module: %s",shortName);
Module module = new Module(baseHome,file);
- return register(module);
+ modules.add(module);
+ names.put(module.getName(),module);
+ if (module.isDynamic())
+ names.put(module.getFilesystemRef(),module);
+ return module;
+ }
+ catch (Error|RuntimeException t)
+ {
+ throw t;
}
catch (Throwable t)
{
- throw new GraphException("Unable to register module: " + shortName,t);
- }
- }
-
- /**
- * Modules can have a different logical name than to their filesystem reference. This updates existing references to
- * the filesystem form to use the logical
- * name form.
- *
- * @param module
- * the module that might have other modules referring to it.
- */
- private void updateParentReferencesTo(Module module)
- {
- if (module.getName().equals(module.getFilesystemRef()))
- {
- // nothing to do, its sane already
- return;
- }
-
- for (Module m : getNodes())
- {
- List resolvedParents = new ArrayList<>();
- for (String parent : m.getParentNames())
- {
- if (parent.equals(module.getFilesystemRef()))
- {
- // use logical name instead
- resolvedParents.add(module.getName());
- }
- else
- {
- // use name as-is
- resolvedParents.add(parent);
- }
- }
- m.setParentNames(resolvedParents);
+ throw new IllegalStateException("Unable to register module: " + shortName,t);
}
}
@@ -247,21 +168,103 @@ public class Modules extends Graph
{
StringBuilder str = new StringBuilder();
str.append("Modules[");
- str.append("count=").append(count());
+ str.append("count=").append(modules.size());
str.append(",<");
- boolean delim = false;
- for (String name : getNodeNames())
+ final AtomicBoolean delim = new AtomicBoolean(false);
+ modules.forEach(m->
{
- if (delim)
- {
+ if (delim.get())
str.append(',');
- }
- str.append(name);
- delim = true;
- }
+ str.append(m.getName());
+ delim.set(true);
+ });
str.append(">");
str.append("]");
return str.toString();
}
+ public void sort()
+ {
+ TopologicalSort sort = new TopologicalSort<>();
+ for (Module module: modules)
+ {
+ Consumer add = name ->
+ {
+ Module dependency = names.get(name);
+ if (dependency!=null)
+ sort.addDependency(module,dependency);
+ };
+ module.getDepends().forEach(add);
+ module.getOptional().forEach(add);
+ }
+ sort.sort(modules);
+ }
+
+ public List getSelected()
+ {
+ return modules.stream().filter(m->{return m.isSelected();}).collect(Collectors.toList());
+ }
+
+ public Set select(String name, String enabledFrom)
+ {
+ Module module = get(name);
+ if (module==null)
+ throw new UsageException(UsageException.ERR_UNKNOWN,"Unknown module='%s'",name);
+
+ Set enabled = new HashSet<>();
+ enable(enabled,module,enabledFrom,false);
+ return enabled;
+ }
+
+ private void enable(Set enabled,Module module, String enabledFrom, boolean transitive)
+ {
+ StartLog.debug("enable %s from %s transitive=%b",module,enabledFrom,transitive);
+ if (module.addSelection(enabledFrom,transitive))
+ {
+ StartLog.debug("enabled %s",module.getName());
+ enabled.add(module.getName());
+ module.expandProperties(args.getProperties());
+ if (module.hasDefaultConfig())
+ {
+ for(String line:module.getDefaultConfig())
+ args.parse(line,module.getFilesystemRef(),false);
+ for (Module m:modules)
+ m.expandProperties(args.getProperties());
+ }
+ }
+ else if (module.isTransitive() && module.hasIniTemplate())
+ enabled.add(module.getName());
+
+ for(String name:module.getDepends())
+ {
+ Module depends = names.get(name);
+ StartLog.debug("%s depends on %s/%s",module,name,depends);
+ if (depends==null)
+ {
+ Path file = baseHome.getPath("modules/" + name + ".mod");
+ depends = registerModule(file);
+ depends.expandProperties(args.getProperties());
+ }
+
+ if (depends!=null)
+ enable(enabled,depends,"transitive from "+module.getName(),true);
+ }
+ }
+
+ public Module get(String name)
+ {
+ return names.get(name);
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return modules.iterator();
+ }
+
+ public Stream stream()
+ {
+ return modules.stream();
+ }
+
}
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 db758d2c185..3b2af0ff55c 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
@@ -156,6 +156,10 @@ public class StartArgs
/** --add-to-start=[module,[module]] */
private List addToStartIni = new ArrayList<>();
+ /** Tri-state True if modules should be added to StartdFirst, false if StartIni first, else null */
+ private Boolean addToStartdFirst;
+
+
// module inspection commands
/** --write-module-graph=[filename] */
private String moduleGraphFilename;
@@ -181,6 +185,7 @@ public class StartArgs
private boolean exec = false;
private String exec_properties;
private boolean approveAllLicenses = false;
+
public StartArgs()
{
@@ -593,7 +598,7 @@ public class StartArgs
Path prop_path;
if (exec_properties==null)
{
- prop_path=Files.createTempFile(Paths.get(baseHome.getBase()), "start_", ".properties");
+ prop_path=Files.createTempFile("start_", ".properties");
prop_path.toFile().deleteOnExit();
}
else
@@ -780,6 +785,13 @@ public class StartArgs
return version;
}
+ public boolean isAddToStartdFirst()
+ {
+ if (addToStartdFirst==null)
+ throw new IllegalStateException();
+ return addToStartdFirst.booleanValue();
+ }
+
public void parse(ConfigSources sources)
{
ListIterator iter = sources.reverseListIterator();
@@ -808,7 +820,7 @@ public class StartArgs
* @param replaceProps
* true if properties in this parse replace previous ones, false to not replace.
*/
- private void parse(final String rawarg, String source, boolean replaceProps)
+ public void parse(final String rawarg, String source, boolean replaceProps)
{
if (rawarg == null)
{
@@ -954,6 +966,8 @@ public class StartArgs
run = false;
download = true;
licenseCheckRequired = true;
+ if (addToStartdFirst==null)
+ addToStartdFirst=Boolean.TRUE;
return;
}
@@ -965,6 +979,8 @@ public class StartArgs
run = false;
download = true;
licenseCheckRequired = true;
+ if (addToStartdFirst==null)
+ addToStartdFirst=Boolean.FALSE;
return;
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java
index 1de331d717c..9f2317477a6 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartDirBuilder.java
@@ -31,7 +31,6 @@ import org.eclipse.jetty.start.BaseHome;
import org.eclipse.jetty.start.FS;
import org.eclipse.jetty.start.Module;
import org.eclipse.jetty.start.StartLog;
-import org.eclipse.jetty.start.graph.OnlyTransitivePredicate;
/**
* Management of the ${jetty.base}/start.d/ based configuration.
@@ -64,13 +63,12 @@ public class StartDirBuilder implements BaseBuilder.Config
}
String mode = "";
- boolean isTransitive = module.matches(OnlyTransitivePredicate.INSTANCE);
- if (isTransitive)
+ if (module.isTransitive())
{
mode = "(transitively) ";
}
- if (module.hasIniTemplate() || !isTransitive)
+ if (module.hasIniTemplate() || !module.isTransitive())
{
// Create start.d/{name}.ini
Path ini = startDir.resolve(module.getName() + ".ini");
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartIniBuilder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartIniBuilder.java
index 035ec201180..f55d2c5f35c 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartIniBuilder.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/builders/StartIniBuilder.java
@@ -35,7 +35,6 @@ import org.eclipse.jetty.start.BaseHome;
import org.eclipse.jetty.start.Module;
import org.eclipse.jetty.start.Props;
import org.eclipse.jetty.start.StartLog;
-import org.eclipse.jetty.start.graph.OnlyTransitivePredicate;
/**
* Management of the ${jetty.base}/start.ini based configuration.
@@ -107,13 +106,12 @@ public class StartIniBuilder implements BaseBuilder.Config
}
String mode = "";
- boolean isTransitive = module.matches(OnlyTransitivePredicate.INSTANCE);
- if (isTransitive)
+ if (module.isTransitive())
{
mode = "(transitively) ";
}
- if (module.hasIniTemplate() || !isTransitive)
+ if (module.hasIniTemplate() || !module.isTransitive())
{
StartLog.info("%-15s initialised %sin %s",module.getName(),mode,baseHome.toShortForm(startIni));
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java
index 76f8e55e190..6c2466b4f86 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/MavenLocalRepoFileInitializer.java
@@ -47,7 +47,7 @@ import org.eclipse.jetty.start.Utils;
*
optional type and classifier requirement
*
*/
-public class MavenLocalRepoFileInitializer extends UriFileInitializer implements FileInitializer
+public class MavenLocalRepoFileInitializer extends UriFileInitializer
{
public static class Coordinates
{
@@ -105,7 +105,7 @@ public class MavenLocalRepoFileInitializer extends UriFileInitializer implements
if (isFilePresent(file, baseHome.getPath(fileRef)))
{
// All done
- return true;
+ return false;
}
// If using local repository
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java
index 0f5c0973ac3..7cadd0b58bc 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/fileinits/UriFileInitializer.java
@@ -54,7 +54,7 @@ public class UriFileInitializer implements FileInitializer
if(isFilePresent(file, baseHome.getPath(fileRef)))
{
// All done
- return true;
+ return false;
}
download(uri,file);
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AllPredicate.java b/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AllPredicate.java
deleted file mode 100644
index 85ea61cb60f..00000000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AllPredicate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.graph;
-
-/**
- * Match on everything.
- */
-public class AllPredicate implements Predicate
-{
- @Override
- public boolean match(Node> node)
- {
- return true;
- }
-}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AndPredicate.java b/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AndPredicate.java
deleted file mode 100644
index 2234c4f1f24..00000000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AndPredicate.java
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.graph;
-
-/**
- * Match on multiple predicates.
- */
-public class AndPredicate implements Predicate
-{
- private final Predicate predicates[];
-
- public AndPredicate(Predicate... predicates)
- {
- this.predicates = predicates;
- }
-
- @Override
- public boolean match(Node> node)
- {
- for (Predicate predicate : this.predicates)
- {
- if (!predicate.match(node))
- {
- return false;
- }
- }
-
- return true;
- }
-}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AnySelectionPredicate.java b/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AnySelectionPredicate.java
deleted file mode 100644
index 667edbc00da..00000000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/AnySelectionPredicate.java
+++ /dev/null
@@ -1,28 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.graph;
-
-public class AnySelectionPredicate implements Predicate
-{
- @Override
- public boolean match(Node> input)
- {
- return !input.getSelections().isEmpty();
- }
-}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/CriteriaPredicate.java b/jetty-start/src/main/java/org/eclipse/jetty/start/graph/CriteriaPredicate.java
deleted file mode 100644
index 416a21689d1..00000000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/CriteriaPredicate.java
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.graph;
-
-/**
- * Predicate against a specific {@link Selection#getCriteria()}
- */
-public class CriteriaPredicate implements Predicate
-{
- private final String criteria;
-
- public CriteriaPredicate(String criteria)
- {
- this.criteria = criteria;
- }
-
- @Override
- public boolean match(Node> node)
- {
- for (Selection selection : node.getSelections())
- {
- if (criteria.equalsIgnoreCase(selection.getCriteria()))
- {
- return true;
- }
- }
- return false;
- }
-}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/CriteriaSetPredicate.java b/jetty-start/src/main/java/org/eclipse/jetty/start/graph/CriteriaSetPredicate.java
deleted file mode 100644
index 82c29f96a22..00000000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/CriteriaSetPredicate.java
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.graph;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Should match against the provided set of {@link Selection#getCriteria()} values.
- *
- * Incomplete set is considered to be no-match.
- */
-public class CriteriaSetPredicate implements Predicate
-{
- private final Set criteriaSet;
-
- public CriteriaSetPredicate(String... criterias)
- {
- this.criteriaSet = new HashSet<>();
-
- for (String name : criterias)
- {
- this.criteriaSet.add(name);
- }
- }
-
- @Override
- public boolean match(Node> node)
- {
- Set selections = node.getSelections();
- if (selections == null)
- {
- // empty sources list
- return false;
- }
-
- Set actualCriterias = node.getSelectedCriteriaSet();
-
- if (actualCriterias.size() != criteriaSet.size())
- {
- // non-equal sized set
- return false;
- }
-
- for (String actualCriteria : actualCriterias)
- {
- if (!this.criteriaSet.contains(actualCriteria))
- {
- return false;
- }
- }
- return true;
- }
-
-}
\ No newline at end of file
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/Graph.java b/jetty-start/src/main/java/org/eclipse/jetty/start/graph/Graph.java
deleted file mode 100644
index 50557f51876..00000000000
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/graph/Graph.java
+++ /dev/null
@@ -1,503 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2015 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.graph;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-
-import org.eclipse.jetty.start.Props;
-import org.eclipse.jetty.start.StartLog;
-import org.eclipse.jetty.start.Utils;
-
-/**
- * Basic Graph
- * @param the node type
- */
-public abstract class Graph> implements Iterable
-{
- private String selectionTerm = "select";
- private String nodeTerm = "node";
- private Map nodes = new LinkedHashMap<>();
- private int maxDepth = -1;
-
- protected Set asNameSet(Set nodeSet)
- {
- Set ret = new HashSet<>();
- for (T node : nodeSet)
- {
- ret.add(node.getName());
- }
- return ret;
- }
-
- private void assertNoCycle(T node, Stack refs)
- {
- for (T parent : node.getParentEdges())
- {
- if (refs.contains(parent.getName()))
- {
- // Cycle detected.
- StringBuilder err = new StringBuilder();
- err.append("A cyclic reference in the ");
- err.append(this.getClass().getSimpleName());
- err.append(" has been detected: ");
- for (int i = 0; i < refs.size(); i++)
- {
- if (i > 0)
- {
- err.append(" -> ");
- }
- err.append(refs.get(i));
- }
- err.append(" -> ").append(parent.getName());
- throw new IllegalStateException(err.toString());
- }
-
- refs.push(parent.getName());
- assertNoCycle(parent,refs);
- refs.pop();
- }
- }
-
- private void bfsCalculateDepth(final T node, final int depthNow)
- {
- int depth = depthNow + 1;
-
- // Set depth on every child first
- for (T child : node.getChildEdges())
- {
- child.setDepth(Math.max(depth,child.getDepth()));
- this.maxDepth = Math.max(this.maxDepth,child.getDepth());
- }
-
- // Dive down
- for (T child : node.getChildEdges())
- {
- bfsCalculateDepth(child,depth);
- }
- }
-
- public void buildGraph() throws FileNotFoundException, IOException
- {
- // Connect edges
- // Make a copy of nodes.values() as the list could be modified
- List nodeList = new ArrayList<>(nodes.values());
- for (T node : nodeList)
- {
- for (String parentName : node.getParentNames())
- {
- T parent = get(parentName);
-
- if (parent == null)
- {
- parent = resolveNode(parentName);
- }
-
- if (parent == null)
- {
- if (Props.hasPropertyKey(parentName))
- {
- StartLog.debug("Module property not expandable (yet) [%s]",parentName);
- }
- else
- {
- StartLog.warn("Module not found [%s]",parentName);
- }
- }
- else
- {
- node.addParentEdge(parent);
- parent.addChildEdge(node);
- }
- }
-
- for (String optionalParentName : node.getOptionalParentNames())
- {
- T optional = get(optionalParentName);
- if (optional == null)
- {
- StartLog.debug("Optional module not found [%s]",optionalParentName);
- }
- else if (optional.isSelected())
- {
- node.addParentEdge(optional);
- optional.addChildEdge(node);
- }
- }
- }
-
- // Verify there is no cyclic references
- Stack refs = new Stack<>();
- for (T module : nodes.values())
- {
- refs.push(module.getName());
- assertNoCycle(module,refs);
- refs.pop();
- }
-
- // Calculate depth of all modules for sorting later
- for (T module : nodes.values())
- {
- if (module.getParentEdges().isEmpty())
- {
- bfsCalculateDepth(module,0);
- }
- }
- }
-
- public boolean containsNode(String name)
- {
- return nodes.containsKey(name);
- }
-
- public int count()
- {
- return nodes.size();
- }
-
- public void dumpSelectedTree()
- {
- List ordered = new ArrayList<>();
- ordered.addAll(nodes.values());
- Collections.sort(ordered,new NodeDepthComparator());
-
- List active = getSelected();
-
- for (T module : ordered)
- {
- if (active.contains(module))
- {
- // Show module name
- String indent = toIndent(module.getDepth());
- boolean transitive = module.matches(OnlyTransitivePredicate.INSTANCE);
- System.out.printf("%s + %s: %s [%s]%n",indent,toCap(nodeTerm),module.getName(),transitive?"transitive":"selected");
- }
- }
- }
-
- public void dumpSelected()
- {
- List ordered = new ArrayList<>();
- ordered.addAll(nodes.values());
- Collections.sort(ordered,new NodeDepthComparator());
-
- List active = getSelected();
-
- for (T module : ordered)
- {
- if (active.contains(module))
- {
- // Show module name
- boolean transitive = module.matches(OnlyTransitivePredicate.INSTANCE);
- System.out.printf(" %3d) %-15s ",module.getDepth() + 1,module.getName());
- if (transitive)
- {
- System.out.println(" ");
- }
- else
- {
- List criterias = new ArrayList<>();
- for (Selection selection : module.getSelections())
- {
- if (selection.isExplicit())
- {
- criterias.add(selection.getCriteria());
- }
- }
- Collections.sort(criterias);
- System.out.println(Utils.join(criterias,", "));
- }
- }
- }
- }
-
- protected void findChildren(T module, Set ret)
- {
- ret.add(module);
- for (T child : module.getChildEdges())
- {
- ret.add(child);
- }
- }
-
- protected void findParents(T module, Map ret)
- {
- ret.put(module.getName(),module);
- for (T parent : module.getParentEdges())
- {
- ret.put(parent.getName(),parent);
- findParents(parent,ret);
- }
- }
-
- public T get(String name)
- {
- return nodes.get(name);
- }
-
- /**
- * Get the list of Selected nodes.
- * @return the list of selected nodes
- */
- public List getSelected()
- {
- return getMatching(new AnySelectionPredicate());
- }
-
- /**
- * Get the Nodes from the tree that match the provided predicate.
- *
- * @param predicate
- * the way to match nodes
- * @return the list of matching nodes in execution order.
- */
- public List getMatching(Predicate predicate)
- {
- List selected = new ArrayList();
-
- for (T node : nodes.values())
- {
- if (predicate.match(node))
- {
- selected.add(node);
- }
- }
-
- Collections.sort(selected,new NodeDepthComparator());
- return selected;
- }
-
- public int getMaxDepth()
- {
- return maxDepth;
- }
-
- public Set getModulesAtDepth(int depth)
- {
- Set ret = new HashSet<>();
- for (T node : nodes.values())
- {
- if (node.getDepth() == depth)
- {
- ret.add(node);
- }
- }
- return ret;
- }
-
- public Collection getNodeNames()
- {
- return nodes.keySet();
- }
-
- public Collection getNodes()
- {
- return nodes.values();
- }
-
- public String getNodeTerm()
- {
- return nodeTerm;
- }
-
- public String getSelectionTerm()
- {
- return selectionTerm;
- }
-
- @Override
- public Iterator iterator()
- {
- return nodes.values().iterator();
- }
-
- public abstract void onNodeSelected(T node);
-
- public T register(T node)
- {
- StartLog.debug("Registering Node: [%s] %s",node.getName(),node);
- nodes.put(node.getName(),node);
- return node;
- }
-
- public Set resolveChildNodesOf(String nodeName)
- {
- Set ret = new HashSet<>();
- T module = get(nodeName);
- findChildren(module,ret);
- return asNameSet(ret);
- }
-
- /**
- * Resolve a node just in time.
- *