Merge branch 'master' into session-refactor
Conflicts: jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionManager.java jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractInvalidationSessionTest.java
This commit is contained in:
commit
9ff55cb301
87
VERSION.txt
87
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
|
jetty-9.3.3.v20150827 - 27 August 2015
|
||||||
+ 470311 Introduce a proxy-protocol module.
|
+ 470311 Introduce a proxy-protocol module.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.1-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.1.0-SNAPSHOT</version>
|
<version>9.1.3-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludes>**/MANIFEST.MF</excludes>
|
<excludes>**/MANIFEST.MF</excludes>
|
||||||
<excludeGroupIds>org.slf4j,org.eclipse.jetty.orbit,org.mortbay.jetty.alpn</excludeGroupIds>
|
<excludeGroupIds>javax.annotations,org.objectweb.asm,javax.servlet,org.slf4j,org.eclipse.jetty.orbit,org.mortbay.jetty.npn</excludeGroupIds>
|
||||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||||
<overWriteReleases>false</overWriteReleases>
|
<overWriteReleases>false</overWriteReleases>
|
||||||
<overWriteSnapshots>true</overWriteSnapshots>
|
<overWriteSnapshots>true</overWriteSnapshots>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>apache-jsp</artifactId>
|
<artifactId>apache-jsp</artifactId>
|
||||||
|
@ -88,8 +88,8 @@
|
||||||
|
|
||||||
<!-- Eclipse Java Compiler (for JSP Compilation) -->
|
<!-- Eclipse Java Compiler (for JSP Compilation) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
<groupId>org.eclipse.jdt.core.compiler</groupId>
|
||||||
<artifactId>org.eclipse.jdt.core</artifactId>
|
<artifactId>ecj</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# Apache JSP Module
|
Enables use of the apache implementation of JSP
|
||||||
#
|
|
||||||
|
|
||||||
[name]
|
[name]
|
||||||
apache-jsp
|
apache-jsp
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>apache-jstl</artifactId>
|
<artifactId>apache-jstl</artifactId>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# Apache JSTL
|
Enables the apache version of JSTL
|
||||||
#
|
|
||||||
|
|
||||||
[name]
|
[name]
|
||||||
apache-jstl
|
apache-jstl
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>example-async-rest</artifactId>
|
<artifactId>example-async-rest</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.eclipse.jetty.example-async-rest</groupId>
|
<groupId>org.eclipse.jetty.example-async-rest</groupId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>example-async-rest</artifactId>
|
<artifactId>example-async-rest</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.eclipse.jetty.example-async-rest</groupId>
|
<groupId>org.eclipse.jetty.example-async-rest</groupId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.examples</groupId>
|
<groupId>org.eclipse.jetty.examples</groupId>
|
||||||
<artifactId>examples-parent</artifactId>
|
<artifactId>examples-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.examples</groupId>
|
<groupId>org.eclipse.jetty.examples</groupId>
|
||||||
<artifactId>examples-parent</artifactId>
|
<artifactId>examples-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
|
@ -55,6 +55,7 @@ import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.servlets.PushCacheFilter;
|
||||||
import org.eclipse.jetty.servlets.PushSessionCacheFilter;
|
import org.eclipse.jetty.servlets.PushSessionCacheFilter;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
@ -74,7 +75,8 @@ public class Http2Server
|
||||||
|
|
||||||
ServletContextHandler context = new ServletContextHandler(server, "/",ServletContextHandler.SESSIONS);
|
ServletContextHandler context = new ServletContextHandler(server, "/",ServletContextHandler.SESSIONS);
|
||||||
context.setResourceBase("src/main/resources/docroot");
|
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.addFilter(PushedTilesFilter.class,"/*",EnumSet.of(DispatcherType.REQUEST));
|
||||||
context.addServlet(new ServletHolder(servlet), "/test/*");
|
context.addServlet(new ServletHolder(servlet), "/test/*");
|
||||||
context.addServlet(DefaultServlet.class, "/").setInitParameter("maxCacheSize","81920");
|
context.addServlet(DefaultServlet.class, "/").setInitParameter("maxCacheSize","81920");
|
||||||
|
|
|
@ -24,10 +24,12 @@ import java.lang.management.ManagementFactory;
|
||||||
|
|
||||||
import org.eclipse.jetty.deploy.DeploymentManager;
|
import org.eclipse.jetty.deploy.DeploymentManager;
|
||||||
import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
|
import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
|
||||||
|
import org.eclipse.jetty.deploy.bindings.DebugListenerBinding;
|
||||||
import org.eclipse.jetty.deploy.providers.WebAppProvider;
|
import org.eclipse.jetty.deploy.providers.WebAppProvider;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.jmx.MBeanContainer;
|
import org.eclipse.jetty.jmx.MBeanContainer;
|
||||||
import org.eclipse.jetty.security.HashLoginService;
|
import org.eclipse.jetty.security.HashLoginService;
|
||||||
|
import org.eclipse.jetty.server.DebugListener;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
@ -155,6 +157,9 @@ public class LikeJettyXml
|
||||||
|
|
||||||
// === jetty-deploy.xml ===
|
// === jetty-deploy.xml ===
|
||||||
DeploymentManager deployer = new DeploymentManager();
|
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.setContexts(contexts);
|
||||||
deployer.setContextAttribute(
|
deployer.setContextAttribute(
|
||||||
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
|
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
|
||||||
|
|
|
@ -52,9 +52,8 @@ public class OneWebApp
|
||||||
WebAppContext webapp = new WebAppContext();
|
WebAppContext webapp = new WebAppContext();
|
||||||
webapp.setContextPath("/");
|
webapp.setContextPath("/");
|
||||||
File warFile = new File(
|
File warFile = new File(
|
||||||
"../../jetty-distribution/target/distribution/test/webapps/test/");
|
"../../tests/test-jmx/jmx-webapp/target/jmx-webapp");
|
||||||
webapp.setWar(warFile.getAbsolutePath());
|
webapp.setWar(warFile.getAbsolutePath());
|
||||||
webapp.addAliasCheck(new AllowSymLinkAliasChecker());
|
|
||||||
|
|
||||||
// A WebAppContext is a ContextHandler as well so it needs to be set to
|
// 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.
|
// the server so it is aware of where to send the appropriate requests.
|
||||||
|
|
|
@ -62,6 +62,7 @@ public class OneWebAppWithJsp
|
||||||
+ warFile.getAbsolutePath() );
|
+ warFile.getAbsolutePath() );
|
||||||
}
|
}
|
||||||
webapp.setWar( warFile.getAbsolutePath() );
|
webapp.setWar( warFile.getAbsolutePath() );
|
||||||
|
webapp.setExtractWAR(true);
|
||||||
|
|
||||||
// This webapp will use jsps and jstl. We need to enable the
|
// This webapp will use jsps and jstl. We need to enable the
|
||||||
// AnnotationConfiguration in order to correctly
|
// AnnotationConfiguration in order to correctly
|
||||||
|
@ -100,6 +101,8 @@ public class OneWebAppWithJsp
|
||||||
|
|
||||||
// Start things up!
|
// Start things up!
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
|
server.dumpStdErr();
|
||||||
|
|
||||||
// The use of server.join() the will make the current thread join and
|
// The use of server.join() the will make the current thread join and
|
||||||
// wait until the server is done executing.
|
// wait until the server is done executing.
|
||||||
|
|
|
@ -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
|
|
@ -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.LEVEL=INFO
|
||||||
org.eclipse.jetty.STACKS=true
|
org.eclipse.jetty.STACKS=true
|
||||||
org.eclipse.jetty.SOURCE=false
|
|
||||||
#org.eclipse.jetty.STACKS=false
|
#org.eclipse.jetty.STACKS=false
|
||||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
|
||||||
#org.eclipse.jetty.io.LEVEL=DEBUG
|
#org.eclipse.jetty.io.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>org.eclipse.jetty.examples</groupId>
|
<groupId>org.eclipse.jetty.examples</groupId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-alpn-parent</artifactId>
|
<artifactId>jetty-alpn-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-alpn-client</artifactId>
|
<artifactId>jetty-alpn-client</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-alpn-parent</artifactId>
|
<artifactId>jetty-alpn-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-alpn-server</artifactId>
|
<artifactId>jetty-alpn-server</artifactId>
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,11 +1,10 @@
|
||||||
# ALPN is provided via a -Xbootclasspath that modifies the secure connections
|
[description]
|
||||||
# in java to support the ALPN layer needed for HTTP/2.
|
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
|
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).
|
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
|
||||||
# The alpn module will use an appropriate alpn-boot jar for your
|
specific version of Java.
|
||||||
# specific version of Java.
|
|
||||||
#
|
#
|
||||||
# IMPORTANT: Versions of Java that exist after this module was created are
|
# IMPORTANT: Versions of Java that exist after this module was created are
|
||||||
# not guaranteed to work with existing alpn-boot jars, and might
|
# not guaranteed to work with existing alpn-boot jars, and might
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-alpn-parent</artifactId>
|
<artifactId>jetty-alpn-parent</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-annotations</artifactId>
|
<artifactId>jetty-annotations</artifactId>
|
||||||
|
@ -20,7 +20,6 @@
|
||||||
<extensions>true</extensions>
|
<extensions>true</extensions>
|
||||||
<configuration>
|
<configuration>
|
||||||
<instructions>
|
<instructions>
|
||||||
<Import-Package>javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=5,*</Import-Package>
|
|
||||||
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
|
<Require-Capability>osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)"</Require-Capability>
|
||||||
</instructions>
|
</instructions>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
#
|
[description]
|
||||||
# Jetty Annotation Scanning Module
|
Enables Annotation scanning for deployed webapplications.
|
||||||
#
|
|
||||||
|
|
||||||
[depend]
|
[depend]
|
||||||
# Annotations needs plus, and jndi features
|
|
||||||
plus
|
plus
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
# Annotations needs jetty annotation jars
|
|
||||||
lib/jetty-annotations-${jetty.version}.jar
|
lib/jetty-annotations-${jetty.version}.jar
|
||||||
# Need annotation processing jars too
|
|
||||||
lib/annotations/*.jar
|
lib/annotations/*.jar
|
||||||
|
|
||||||
[xml]
|
[xml]
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
||||||
protected CounterStatistic _webInfLibStats;
|
protected CounterStatistic _webInfLibStats;
|
||||||
protected CounterStatistic _webInfClassesStats;
|
protected CounterStatistic _webInfClassesStats;
|
||||||
protected Pattern _sciExcludePattern;
|
protected Pattern _sciExcludePattern;
|
||||||
|
protected ServiceLoader<ServletContainerInitializer> _loadedInitializers = null;
|
||||||
/**
|
/**
|
||||||
* TimeStatistic
|
* TimeStatistic
|
||||||
*
|
*
|
||||||
|
@ -413,6 +413,9 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
||||||
context.removeBean(starter);
|
context.removeBean(starter);
|
||||||
context.removeAttribute(CONTAINER_INITIALIZER_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();
|
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<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
|
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
|
||||||
throws Exception
|
throws Exception
|
||||||
{
|
{
|
||||||
ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
|
ArrayList<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
|
||||||
|
|
||||||
//We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
|
//We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
|
||||||
long start = 0;
|
long start = 0;
|
||||||
|
|
||||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||||
ServiceLoader<ServletContainerInitializer> loadedInitializers = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
start = System.nanoTime();
|
start = System.nanoTime();
|
||||||
Thread.currentThread().setContextClassLoader(context.getClassLoader());
|
Thread.currentThread().setContextClassLoader(context.getClassLoader());
|
||||||
loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
|
_loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -847,21 +856,22 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
|
LOG.debug("Service loaders found in {}ms", (TimeUnit.MILLISECONDS.convert((System.nanoTime()-start), TimeUnit.NANOSECONDS)));
|
||||||
|
|
||||||
|
|
||||||
Map<ServletContainerInitializer,Resource> sciResourceMap = new HashMap<ServletContainerInitializer,Resource>();
|
Map<ServletContainerInitializer,Resource> sciResourceMap = new HashMap<ServletContainerInitializer,Resource>();
|
||||||
ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
|
ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);
|
||||||
|
|
||||||
//Get initial set of SCIs that aren't from excluded jars or excluded by the containerExclusionPattern, or excluded
|
//Get initial set of SCIs that aren't from excluded jars or excluded by the containerExclusionPattern, or excluded
|
||||||
//because containerInitializerOrdering omits it
|
//because containerInitializerOrdering omits it
|
||||||
for (ServletContainerInitializer sci:loadedInitializers)
|
for (ServletContainerInitializer sci:_loadedInitializers)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (matchesExclusionPattern(sci))
|
if (matchesExclusionPattern(sci))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
|
if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Resource sciResource = getJarFor(sci);
|
Resource sciResource = getJarFor(sci);
|
||||||
if (isFromExcludedJar(context, sci, sciResource))
|
if (isFromExcludedJar(context, sci, sciResource))
|
||||||
{
|
{
|
||||||
|
@ -921,7 +931,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
||||||
{
|
{
|
||||||
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
|
for (Map.Entry<ServletContainerInitializer, Resource> entry:sciResourceMap.entrySet())
|
||||||
{
|
{
|
||||||
if (webInfJar.equals(entry.getValue()))
|
if (webInfJar.equals(entry.getValue()))
|
||||||
nonExcludedInitializers.add(entry.getKey());
|
nonExcludedInitializers.add(entry.getKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -933,7 +943,8 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
||||||
int i=0;
|
int i=0;
|
||||||
for (ServletContainerInitializer sci:nonExcludedInitializers)
|
for (ServletContainerInitializer sci:nonExcludedInitializers)
|
||||||
LOG.debug("ServletContainerInitializer: {} {} from {}",(++i), sci.getClass().getName(), sciResourceMap.get(sci));
|
LOG.debug("ServletContainerInitializer: {} {} from {}",(++i), sci.getClass().getName(), sciResourceMap.get(sci));
|
||||||
}
|
}
|
||||||
|
|
||||||
return nonExcludedInitializers;
|
return nonExcludedInitializers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -558,11 +558,14 @@ public class AnnotationParser
|
||||||
if (!isParsed(className) || resolver.shouldOverride(className))
|
if (!isParsed(className) || resolver.shouldOverride(className))
|
||||||
{
|
{
|
||||||
className = className.replace('.', '/')+".class";
|
className = className.replace('.', '/')+".class";
|
||||||
URL resource = Loader.getResource(this.getClass(), className);
|
URL resource = Loader.getResource(className);
|
||||||
if (resource!= null)
|
if (resource!= null)
|
||||||
{
|
{
|
||||||
Resource r = Resource.newResource(resource);
|
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()))
|
if (!isParsed(cz.getName()) || resolver.shouldOverride(cz.getName()))
|
||||||
{
|
{
|
||||||
String nameAsResource = cz.getName().replace('.', '/')+".class";
|
String nameAsResource = cz.getName().replace('.', '/')+".class";
|
||||||
URL resource = Loader.getResource(this.getClass(), nameAsResource);
|
URL resource = Loader.getResource(nameAsResource);
|
||||||
if (resource!= null)
|
if (resource!= null)
|
||||||
{
|
{
|
||||||
Resource r = Resource.newResource(resource);
|
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))))
|
if ((resolver == null) || (!resolver.isExcluded(s) && (!isParsed(s) || resolver.shouldOverride(s))))
|
||||||
{
|
{
|
||||||
s = s.replace('.', '/')+".class";
|
s = s.replace('.', '/')+".class";
|
||||||
URL resource = Loader.getResource(this.getClass(), s);
|
URL resource = Loader.getResource(s);
|
||||||
if (resource!= null)
|
if (resource!= null)
|
||||||
{
|
{
|
||||||
Resource r = Resource.newResource(resource);
|
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"))
|
if (fullname.endsWith(".class"))
|
||||||
{
|
{
|
||||||
scanClass(handlers, null, r.getInputStream());
|
try (InputStream is=r.getInputStream())
|
||||||
return;
|
{
|
||||||
|
scanClass(handlers, null, is);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r);
|
if (LOG.isDebugEnabled()) LOG.warn("Resource not scannable for classes: {}", r);
|
||||||
|
@ -963,11 +975,14 @@ public class AnnotationParser
|
||||||
|
|
||||||
if ((resolver == null)
|
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);
|
Resource clazz = Resource.newResource("jar:"+jar.getURI()+"!/"+name);
|
||||||
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
|
if (LOG.isDebugEnabled()) {LOG.debug("Scanning class from jar {}", clazz);};
|
||||||
scanClass(handlers, jar, clazz.getInputStream());
|
try (InputStream is = clazz.getInputStream())
|
||||||
|
{
|
||||||
|
scanClass(handlers, jar, is);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,7 +201,7 @@ public class Util
|
||||||
}
|
}
|
||||||
case Type.OBJECT:
|
case Type.OBJECT:
|
||||||
{
|
{
|
||||||
return (Loader.loadClass(null, t.getClassName()));
|
return (Loader.loadClass(t.getClassName()));
|
||||||
}
|
}
|
||||||
case Type.SHORT:
|
case Type.SHORT:
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-ant</artifactId>
|
<artifactId>jetty-ant</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
<artifactId>jetty-cdi-parent</artifactId>
|
<artifactId>jetty-cdi-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>cdi-core</artifactId>
|
<artifactId>cdi-core</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
<artifactId>jetty-cdi-parent</artifactId>
|
<artifactId>jetty-cdi-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>cdi-full-servlet</artifactId>
|
<artifactId>cdi-full-servlet</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
<artifactId>jetty-cdi-parent</artifactId>
|
<artifactId>jetty-cdi-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>cdi-servlet</artifactId>
|
<artifactId>cdi-servlet</artifactId>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# [EXPERIMENTAL] CDI / Weld Jetty module
|
Experimental CDI/Weld integration
|
||||||
#
|
|
||||||
|
|
||||||
[depend]
|
[depend]
|
||||||
deploy
|
deploy
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
<artifactId>jetty-cdi-parent</artifactId>
|
<artifactId>jetty-cdi-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>cdi-websocket</artifactId>
|
<artifactId>cdi-websocket</artifactId>
|
||||||
|
|
|
@ -57,6 +57,7 @@ public class ScopeBasicsTest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validation of Scope / Inject logic on non-websocket-scoped classes
|
* Validation of Scope / Inject logic on non-websocket-scoped classes
|
||||||
|
* @throws Exception on test failure
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testBasicBehavior() throws Exception
|
public void testBasicBehavior() throws Exception
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class WebSocketScopeBaselineTest
|
||||||
* Test behavior of {@link WebSocketScope} in basic operation.
|
* Test behavior of {@link WebSocketScope} in basic operation.
|
||||||
* <p>
|
* <p>
|
||||||
* Food is declared as part of WebSocketScope, and as such, only 1 instance of it can exist.
|
* Food is declared as part of WebSocketScope, and as such, only 1 instance of it can exist.
|
||||||
|
* @throws Exception on test failure
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testScopeBehavior() throws Exception
|
public void testScopeBehavior() throws Exception
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
<artifactId>jetty-cdi-parent</artifactId>
|
<artifactId>jetty-cdi-parent</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>test-cdi-webapp</artifactId>
|
<artifactId>test-cdi-webapp</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -15,23 +15,6 @@
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.felix</groupId>
|
|
||||||
<artifactId>maven-bundle-plugin</artifactId>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>manifest</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<instructions>
|
|
||||||
<Import-Package>javax.net.*,*</Import-Package>
|
|
||||||
</instructions>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>findbugs-maven-plugin</artifactId>
|
<artifactId>findbugs-maven-plugin</artifactId>
|
||||||
|
@ -65,6 +48,44 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>2.4.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||||
|
<shadedClassifierName>hybrid</shadedClassifierName>
|
||||||
|
<artifactSet>
|
||||||
|
<includes>
|
||||||
|
<include>org.eclipse.jetty:jetty-http</include>
|
||||||
|
<include>org.eclipse.jetty:jetty-io</include>
|
||||||
|
<include>org.eclipse.jetty:jetty-util</include>
|
||||||
|
</includes>
|
||||||
|
</artifactSet>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.http</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.client.shaded.http</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.io</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.client.shaded.io</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.util</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.client.shaded.util</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# Client Feature
|
Adds the Jetty HTTP client to the server classpath.
|
||||||
#
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
lib/jetty-client-${jetty.version}.jar
|
lib/jetty-client-${jetty.version}.jar
|
||||||
|
|
|
@ -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<Connection>()
|
||||||
|
{
|
||||||
|
@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<Connection> connections)
|
||||||
|
{
|
||||||
|
connections.forEach(Connection::close);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String dump()
|
||||||
|
{
|
||||||
|
return ContainerLifeCycle.dump(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
import java.nio.channels.SelectableChannel;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.Map;
|
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.ManagedSelector;
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
import org.eclipse.jetty.io.SelectorManager;
|
||||||
|
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
|
@ -173,13 +175,15 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@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")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||||
|
@ -188,7 +192,7 @@ public abstract class AbstractHttpClientTransport extends ContainerLifeCycle imp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void connectionFailed(SocketChannel channel, Throwable x, Object attachment)
|
protected void connectionFailed(SelectableChannel channel, Throwable x, Object attachment)
|
||||||
{
|
{
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, Object> context = (Map<String, Object>)attachment;
|
Map<String, Object> context = (Map<String, Object>)attachment;
|
||||||
|
|
|
@ -37,7 +37,7 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
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);
|
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 Pattern AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\"(.*)", Pattern.CASE_INSENSITIVE);
|
||||||
private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
|
private static final String AUTHENTICATION_ATTRIBUTE = AuthenticationProtocolHandler.class.getName() + ".authentication";
|
||||||
|
|
|
@ -19,424 +19,23 @@
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.Closeable;
|
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.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 interface ConnectionPool extends Closeable
|
||||||
public class ConnectionPool implements Closeable, Dumpable, Sweeper.Sweepable
|
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ConnectionPool.class);
|
boolean isActive(Connection connection);
|
||||||
|
|
||||||
private final AtomicInteger connectionCount = new AtomicInteger();
|
boolean isEmpty();
|
||||||
private final ReentrantLock lock = new ReentrantLock();
|
|
||||||
private final Destination destination;
|
|
||||||
private final int maxConnections;
|
|
||||||
private final Callback requester;
|
|
||||||
private final Deque<Connection> idleConnections;
|
|
||||||
private final Queue<Connection> activeConnections;
|
|
||||||
|
|
||||||
public ConnectionPool(Destination destination, int maxConnections, Callback requester)
|
boolean isClosed();
|
||||||
{
|
|
||||||
this.destination = destination;
|
|
||||||
this.maxConnections = maxConnections;
|
|
||||||
this.requester = requester;
|
|
||||||
this.idleConnections = new LinkedBlockingDeque<>(maxConnections);
|
|
||||||
this.activeConnections = new BlockingArrayQueue<>(maxConnections);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ManagedAttribute(value = "The number of connections", readonly = true)
|
Connection acquire();
|
||||||
public int getConnectionCount()
|
|
||||||
{
|
|
||||||
return connectionCount.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ManagedAttribute(value = "The number of idle connections", readonly = true)
|
boolean release(Connection connection);
|
||||||
public int getIdleConnectionCount()
|
|
||||||
{
|
|
||||||
return idleConnections.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@ManagedAttribute(value = "The number of active connections", readonly = true)
|
boolean remove(Connection connection);
|
||||||
public int getActiveConnectionCount()
|
|
||||||
{
|
|
||||||
return activeConnections.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Queue<Connection> getIdleConnections()
|
|
||||||
{
|
|
||||||
return idleConnections;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Queue<Connection> 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<Connection>()
|
|
||||||
{
|
|
||||||
@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<Connection> idles = new ArrayList<>();
|
|
||||||
List<Connection> 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String dump()
|
void close();
|
||||||
{
|
|
||||||
return ContainerLifeCycle.dump(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
|
||||||
{
|
|
||||||
List<Connection> actives = new ArrayList<>();
|
|
||||||
List<Connection> 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<Sweeper.Sweepable> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Connection> idleConnections;
|
||||||
|
private final Set<Connection> 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<Connection> getIdleConnections()
|
||||||
|
{
|
||||||
|
return idleConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Connection> 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<Connection> 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<Connection> 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<Connection> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -129,6 +129,11 @@ public abstract class HttpChannel
|
||||||
return getHttpReceiver().abort(exchange, failure);
|
return getHttpReceiver().abort(exchange, failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Result exchangeTerminating(HttpExchange exchange, Result result)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||||
{
|
{
|
||||||
disassociate(exchange);
|
disassociate(exchange);
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
import java.net.CookieStore;
|
import java.net.CookieStore;
|
||||||
|
@ -498,24 +497,18 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
if (destination == null)
|
if (destination == null)
|
||||||
{
|
{
|
||||||
destination = transport.newHttpDestination(origin);
|
destination = transport.newHttpDestination(origin);
|
||||||
if (isRunning())
|
addManaged(destination);
|
||||||
|
HttpDestination existing = destinations.putIfAbsent(origin, destination);
|
||||||
|
if (existing != null)
|
||||||
{
|
{
|
||||||
HttpDestination existing = destinations.putIfAbsent(origin, destination);
|
removeBean(destination);
|
||||||
if (existing != null)
|
destination = existing;
|
||||||
{
|
}
|
||||||
destination = existing;
|
else
|
||||||
}
|
{
|
||||||
else
|
if (LOG.isDebugEnabled())
|
||||||
{
|
LOG.debug("Created {}", destination);
|
||||||
addManaged(destination);
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Created {}", destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRunning())
|
|
||||||
removeDestination(destination);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
@ -531,15 +524,12 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
*/
|
*/
|
||||||
public List<Destination> getDestinations()
|
public List<Destination> getDestinations()
|
||||||
{
|
{
|
||||||
return new ArrayList<Destination>(destinations.values());
|
return new ArrayList<>(destinations.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
|
protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
|
||||||
{
|
{
|
||||||
String scheme = request.getScheme().toLowerCase(Locale.ENGLISH);
|
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);
|
String host = request.getHost().toLowerCase(Locale.ENGLISH);
|
||||||
HttpDestination destination = destinationFor(scheme, host, request.getPort());
|
HttpDestination destination = destinationFor(scheme, host, request.getPort());
|
||||||
destination.send(request, listeners);
|
destination.send(request, listeners);
|
||||||
|
@ -994,7 +984,7 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
* anymore and leave space for new destinations.
|
* anymore and leave space for new destinations.
|
||||||
*
|
*
|
||||||
* @param removeIdleDestinations whether destinations that have no connections should be removed
|
* @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)
|
public void setRemoveIdleDestinations(boolean removeIdleDestinations)
|
||||||
{
|
{
|
||||||
|
@ -1047,19 +1037,25 @@ public class HttpClient extends ContainerLifeCycle
|
||||||
|
|
||||||
protected int normalizePort(String scheme, int port)
|
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)
|
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 boolean isSchemeSecure(String scheme)
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
|
||||||
{
|
{
|
||||||
dumpThis(out);
|
return HttpScheme.HTTPS.is(scheme) || HttpScheme.WSS.is(scheme);
|
||||||
dump(out, indent, getBeans(), destinations.values());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
|
private class ContentDecoderFactorySet implements Set<ContentDecoder.Factory>
|
||||||
|
|
|
@ -67,11 +67,13 @@ public class HttpContent implements Callback, Closeable
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpContent.class);
|
private static final Logger LOG = Log.getLogger(HttpContent.class);
|
||||||
private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
|
private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
|
||||||
|
private static final ByteBuffer CLOSE = ByteBuffer.allocate(0);
|
||||||
|
|
||||||
private final ContentProvider provider;
|
private final ContentProvider provider;
|
||||||
private final Iterator<ByteBuffer> iterator;
|
private final Iterator<ByteBuffer> iterator;
|
||||||
private ByteBuffer buffer;
|
private ByteBuffer buffer;
|
||||||
private volatile ByteBuffer content;
|
private ByteBuffer content;
|
||||||
|
private boolean last;
|
||||||
|
|
||||||
public HttpContent(ContentProvider provider)
|
public HttpContent(ContentProvider provider)
|
||||||
{
|
{
|
||||||
|
@ -92,7 +94,7 @@ public class HttpContent implements Callback, Closeable
|
||||||
*/
|
*/
|
||||||
public boolean isLast()
|
public boolean isLast()
|
||||||
{
|
{
|
||||||
return !iterator.hasNext();
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,41 +126,50 @@ public class HttpContent implements Callback, Closeable
|
||||||
*/
|
*/
|
||||||
public boolean advance()
|
public boolean advance()
|
||||||
{
|
{
|
||||||
boolean advanced;
|
|
||||||
boolean hasNext;
|
|
||||||
ByteBuffer bytes;
|
|
||||||
if (iterator instanceof Synchronizable)
|
if (iterator instanceof Synchronizable)
|
||||||
{
|
{
|
||||||
synchronized (((Synchronizable)iterator).getLock())
|
synchronized (((Synchronizable)iterator).getLock())
|
||||||
{
|
{
|
||||||
advanced = iterator.hasNext();
|
return advance(iterator);
|
||||||
bytes = advanced ? iterator.next() : null;
|
|
||||||
hasNext = advanced && iterator.hasNext();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
advanced = iterator.hasNext();
|
return advance(iterator);
|
||||||
bytes = advanced ? iterator.next() : null;
|
|
||||||
hasNext = advanced && iterator.hasNext();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (advanced)
|
private boolean advance(Iterator<ByteBuffer> 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;
|
buffer = bytes;
|
||||||
content = bytes == null ? null : bytes.slice();
|
content = bytes == null ? null : bytes.slice();
|
||||||
if (LOG.isDebugEnabled())
|
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;
|
return bytes != null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (content != AFTER)
|
// No more content, but distinguish between last and consumed.
|
||||||
|
if (wasLast)
|
||||||
{
|
{
|
||||||
content = buffer = AFTER;
|
buffer = content = AFTER;
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Advanced content past last chunk");
|
LOG.debug("Advanced content past last chunk");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer = content = CLOSE;
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Advanced content to last chunk");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +179,7 @@ public class HttpContent implements Callback, Closeable
|
||||||
*/
|
*/
|
||||||
public boolean isConsumed()
|
public boolean isConsumed()
|
||||||
{
|
{
|
||||||
return content == AFTER;
|
return buffer == AFTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -176,6 +187,8 @@ public class HttpContent implements Callback, Closeable
|
||||||
{
|
{
|
||||||
if (isConsumed())
|
if (isConsumed())
|
||||||
return;
|
return;
|
||||||
|
if (buffer == CLOSE)
|
||||||
|
return;
|
||||||
if (iterator instanceof Callback)
|
if (iterator instanceof Callback)
|
||||||
((Callback)iterator).succeeded();
|
((Callback)iterator).succeeded();
|
||||||
}
|
}
|
||||||
|
@ -185,6 +198,8 @@ public class HttpContent implements Callback, Closeable
|
||||||
{
|
{
|
||||||
if (isConsumed())
|
if (isConsumed())
|
||||||
return;
|
return;
|
||||||
|
if (buffer == CLOSE)
|
||||||
|
return;
|
||||||
if (iterator instanceof Callback)
|
if (iterator instanceof Callback)
|
||||||
((Callback)iterator).failed(x);
|
((Callback)iterator).failed(x);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,19 +22,21 @@ import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.channels.AsynchronousCloseException;
|
import java.nio.channels.AsynchronousCloseException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
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.Response;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
|
||||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
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.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
import org.eclipse.jetty.util.thread.Sweeper;
|
||||||
|
|
||||||
@ManagedObject
|
@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);
|
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 ProxyConfiguration.Proxy proxy;
|
||||||
private final ClientConnectionFactory connectionFactory;
|
private final ClientConnectionFactory connectionFactory;
|
||||||
private final HttpField hostField;
|
private final HttpField hostField;
|
||||||
|
private ConnectionPool connectionPool;
|
||||||
|
|
||||||
public HttpDestination(HttpClient client, Origin origin)
|
public HttpDestination(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
|
@ -76,7 +80,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (HttpScheme.HTTPS.is(getScheme()))
|
if (isSecure())
|
||||||
connectionFactory = newSslClientConnectionFactory(connectionFactory);
|
connectionFactory = newSslClientConnectionFactory(connectionFactory);
|
||||||
}
|
}
|
||||||
this.connectionFactory = connectionFactory;
|
this.connectionFactory = connectionFactory;
|
||||||
|
@ -87,6 +91,29 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
||||||
hostField = new HttpField(HttpHeader.HOST, host);
|
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<HttpExchange> newExchangeQueue(HttpClient client)
|
protected Queue<HttpExchange> newExchangeQueue(HttpClient client)
|
||||||
{
|
{
|
||||||
return new BlockingArrayQueue<>(client.getMaxRequestsQueuedPerDestination());
|
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);
|
return new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSecure()
|
||||||
|
{
|
||||||
|
return client.isSchemeSecure(getScheme());
|
||||||
|
}
|
||||||
|
|
||||||
public HttpClient getHttpClient()
|
public HttpClient getHttpClient()
|
||||||
{
|
{
|
||||||
return client;
|
return client;
|
||||||
|
@ -171,6 +203,24 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
||||||
return hostField;
|
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<Response.ResponseListener> listeners)
|
protected void send(HttpRequest request, List<Response.ResponseListener> listeners)
|
||||||
{
|
{
|
||||||
if (!getScheme().equalsIgnoreCase(request.getScheme()))
|
if (!getScheme().equalsIgnoreCase(request.getScheme()))
|
||||||
|
@ -217,7 +267,59 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
||||||
return queue.offer(exchange);
|
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<Connection> promise)
|
public void newConnection(Promise<Connection> promise)
|
||||||
{
|
{
|
||||||
|
@ -239,14 +341,67 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
||||||
abort(new AsynchronousCloseException());
|
abort(new AsynchronousCloseException());
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Closed {}", this);
|
LOG.debug("Closed {}", this);
|
||||||
|
connectionPool.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release(Connection connection)
|
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)
|
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
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
{
|
{
|
||||||
ContainerLifeCycle.dumpObject(out, toString());
|
ContainerLifeCycle.dumpObject(out, toString());
|
||||||
|
ContainerLifeCycle.dump(out, indent, Collections.singletonList(connectionPool));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String asString()
|
public String asString()
|
||||||
|
@ -284,11 +440,12 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
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(),
|
HttpDestination.class.getSimpleName(),
|
||||||
asString(),
|
asString(),
|
||||||
hashCode(),
|
hashCode(),
|
||||||
proxy == null ? "" : "(via " + proxy + ")",
|
proxy == null ? "" : "(via " + proxy + ")",
|
||||||
exchanges.size());
|
exchanges.size(),
|
||||||
|
connectionPool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
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.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
@ -108,7 +107,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
||||||
public void succeeded(Connection connection)
|
public void succeeded(Connection connection)
|
||||||
{
|
{
|
||||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
if (destination.isSecure())
|
||||||
{
|
{
|
||||||
SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
|
SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
|
||||||
if (sslContextFactory != null)
|
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",
|
String message = String.format("Cannot perform requests over SSL, no %s in %s",
|
||||||
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
|
SslContextFactory.class.getSimpleName(), HttpClient.class.getSimpleName());
|
||||||
promise.failed(new IllegalStateException(message));
|
tunnelFailed(new IllegalStateException(message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -131,7 +130,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable x)
|
public void failed(Throwable x)
|
||||||
{
|
{
|
||||||
promise.failed(x);
|
tunnelFailed(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tunnel(HttpDestination destination, final Connection connection)
|
private void tunnel(HttpDestination destination, final Connection connection)
|
||||||
|
@ -139,33 +138,31 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
||||||
String target = destination.getOrigin().getAddress().asString();
|
String target = destination.getOrigin().getAddress().asString();
|
||||||
Origin.Address proxyAddress = destination.getConnectAddress();
|
Origin.Address proxyAddress = destination.getConnectAddress();
|
||||||
HttpClient httpClient = destination.getHttpClient();
|
HttpClient httpClient = destination.getHttpClient();
|
||||||
|
long connectTimeout = httpClient.getConnectTimeout();
|
||||||
Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
|
Request connect = httpClient.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
|
||||||
.scheme(HttpScheme.HTTP.asString())
|
.scheme(HttpScheme.HTTP.asString())
|
||||||
.method(HttpMethod.CONNECT)
|
.method(HttpMethod.CONNECT)
|
||||||
.path(target)
|
.path(target)
|
||||||
.header(HttpHeader.HOST, 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
|
if (result.isFailed())
|
||||||
public void onComplete(Result result)
|
|
||||||
{
|
{
|
||||||
if (result.isFailed())
|
tunnelFailed(result.getFailure());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Response response = result.getResponse();
|
||||||
|
if (response.getStatus() == 200)
|
||||||
{
|
{
|
||||||
tunnelFailed(result.getFailure());
|
tunnelSucceeded();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Response response = result.getResponse();
|
tunnelFailed(new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
|
||||||
if (response.getStatus() == 200)
|
|
||||||
{
|
|
||||||
tunnelSucceeded();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
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);
|
ClientConnectionFactory sslConnectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
|
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
|
||||||
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
||||||
Helper.replaceConnection(oldConnection, newConnection);
|
endPoint.upgrade(newConnection);
|
||||||
// Avoid setting fill interest in the old Connection,
|
|
||||||
// without closing the underlying EndPoint.
|
|
||||||
oldConnection.softClose();
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
||||||
}
|
}
|
||||||
|
@ -198,7 +192,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
||||||
private void tunnelFailed(Throwable failure)
|
private void tunnelFailed(Throwable failure)
|
||||||
{
|
{
|
||||||
endPoint.close();
|
endPoint.close();
|
||||||
failed(failure);
|
promise.failed(failure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -437,6 +437,7 @@ public abstract class HttpReceiver
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
|
result = channel.exchangeTerminating(exchange, result);
|
||||||
boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
|
boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
|
||||||
if (!ordered)
|
if (!ordered)
|
||||||
channel.exchangeTerminated(exchange, result);
|
channel.exchangeTerminated(exchange, result);
|
||||||
|
|
|
@ -20,7 +20,7 @@ package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
|
||||||
public class HttpRequestException extends Throwable
|
public class HttpRequestException extends RuntimeException
|
||||||
{
|
{
|
||||||
private final Request request;
|
private final Request request;
|
||||||
|
|
||||||
|
|
|
@ -376,6 +376,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
result = channel.exchangeTerminating(exchange, result);
|
||||||
HttpDestination destination = getHttpChannel().getHttpDestination();
|
HttpDestination destination = getHttpChannel().getHttpDestination();
|
||||||
boolean ordered = destination.getHttpClient().isStrictEventOrdering();
|
boolean ordered = destination.getHttpClient().isStrictEventOrdering();
|
||||||
if (!ordered)
|
if (!ordered)
|
||||||
|
@ -678,7 +679,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
{
|
{
|
||||||
return content.isNonBlocking();
|
return content.isNonBlocking();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
{
|
{
|
||||||
|
@ -811,9 +812,9 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
boolean advanced = content.advance();
|
boolean advanced = content.advance();
|
||||||
boolean consumed = content.isConsumed();
|
boolean lastContent = content.isLast();
|
||||||
if (LOG.isDebugEnabled())
|
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)
|
if (advanced)
|
||||||
{
|
{
|
||||||
|
@ -821,7 +822,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
return Action.SCHEDULED;
|
return Action.SCHEDULED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumed)
|
if (lastContent)
|
||||||
{
|
{
|
||||||
sendContent(exchange, content, lastCallback);
|
sendContent(exchange, content, lastCallback);
|
||||||
return Action.IDLE;
|
return Action.IDLE;
|
||||||
|
@ -894,7 +895,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
{
|
{
|
||||||
return content.isNonBlocking();
|
return content.isNonBlocking();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.eclipse.jetty.util.LeakDetector;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
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);
|
private static final Logger LOG = Log.getLogger(LeakTrackingConnectionPool.class);
|
||||||
|
|
||||||
|
|
|
@ -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<Holder> idleConnections;
|
||||||
|
private final Map<Connection, Holder> muxedConnections;
|
||||||
|
private final Map<Connection, Holder> 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<Holder> 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<Connection> 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<Holder> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,136 +18,16 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
public abstract class MultiplexHttpDestination extends HttpDestination
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
|
||||||
import org.eclipse.jetty.util.Promise;
|
|
||||||
|
|
||||||
public abstract class MultiplexHttpDestination<C extends Connection> extends HttpDestination implements Promise<Connection>
|
|
||||||
{
|
{
|
||||||
private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
|
|
||||||
private C connection;
|
|
||||||
|
|
||||||
protected MultiplexHttpDestination(HttpClient client, Origin origin)
|
protected MultiplexHttpDestination(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
super(client, origin);
|
super(client, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected ConnectionPool newConnectionPool(HttpClient client)
|
||||||
public void send()
|
|
||||||
{
|
{
|
||||||
while (true)
|
return new MultiplexConnectionPool(this, client.getMaxConnectionsPerDestination(), this,
|
||||||
{
|
client.getMaxRequestsQueuedPerDestination());
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,200 +18,15 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import java.io.IOException;
|
public abstract class PoolingHttpDestination extends HttpDestination
|
||||||
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<C extends Connection> extends HttpDestination implements Callback
|
|
||||||
{
|
{
|
||||||
private final ConnectionPool connectionPool;
|
|
||||||
|
|
||||||
public PoolingHttpDestination(HttpClient client, Origin origin)
|
public PoolingHttpDestination(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
super(client, 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)
|
protected ConnectionPool newConnectionPool(HttpClient client)
|
||||||
{
|
{
|
||||||
return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this);
|
return new DuplexConnectionPool(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Processes a new connection making it idle or active depending on whether requests are waiting to be sent.</p>
|
|
||||||
* <p>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.</p>
|
|
||||||
* <p>If a request is waiting to be executed, it will be dequeued and executed by the new connection.</p>
|
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,7 @@ public class ResponseNotifier
|
||||||
notifyHeaders(listeners, response);
|
notifyHeaders(listeners, response);
|
||||||
if (response instanceof ContentResponse)
|
if (response instanceof ContentResponse)
|
||||||
// TODO: handle callback
|
// 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);
|
notifySuccess(listeners, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ public class ResponseNotifier
|
||||||
notifyHeaders(listeners, response);
|
notifyHeaders(listeners, response);
|
||||||
if (response instanceof ContentResponse)
|
if (response instanceof ContentResponse)
|
||||||
// TODO: handle callback
|
// 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);
|
notifyFailure(listeners, response, failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
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);
|
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
HttpClient client = destination.getHttpClient();
|
HttpClient client = destination.getHttpClient();
|
||||||
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||||
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
if (destination.isSecure())
|
||||||
connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
connectionFactory = new SslClientConnectionFactory(client.getSslContextFactory(), client.getByteBufferPool(), client.getExecutor(), connectionFactory);
|
||||||
org.eclipse.jetty.io.Connection connection = connectionFactory.newConnection(getEndPoint(), context);
|
org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context);
|
||||||
ClientConnectionFactory.Helper.replaceConnection(this, connection);
|
getEndPoint().upgrade(newConnection);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("SOCKS4 tunnel established: {} over {}", this, connection);
|
LOG.debug("SOCKS4 tunnel established: {} over {}", this, newConnection);
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.Scheduler;
|
import org.eclipse.jetty.util.thread.Scheduler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A {@link ConnectionPool} that validates connections before
|
* <p>A connection pool that validates connections before
|
||||||
* making them available for use.</p>
|
* making them available for use.</p>
|
||||||
* <p>Connections that have just been opened are not validated.
|
* <p>Connections that have just been opened are not validated.
|
||||||
* Connections that are {@link #release(Connection) released} will
|
* 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
|
* tuning the idle timeout of the servers to be larger than
|
||||||
* that of the client.</p>
|
* that of the client.</p>
|
||||||
*/
|
*/
|
||||||
public class ValidatingConnectionPool extends ConnectionPool
|
public class ValidatingConnectionPool extends DuplexConnectionPool
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ValidatingConnectionPool.class);
|
private static final Logger LOG = Log.getLogger(ValidatingConnectionPool.class);
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ public class ValidatingConnectionPool extends ConnectionPool
|
||||||
private class Holder implements Runnable
|
private class Holder implements Runnable
|
||||||
{
|
{
|
||||||
private final long timestamp = System.nanoTime();
|
private final long timestamp = System.nanoTime();
|
||||||
private final AtomicBoolean latch = new AtomicBoolean();
|
private final AtomicBoolean done = new AtomicBoolean();
|
||||||
private final Connection connection;
|
private final Connection connection;
|
||||||
public Scheduler.Task task;
|
public Scheduler.Task task;
|
||||||
|
|
||||||
|
@ -166,30 +166,31 @@ public class ValidatingConnectionPool extends ConnectionPool
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
if (latch.compareAndSet(false, true))
|
if (done.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
boolean idle;
|
boolean closed = isClosed();
|
||||||
lock();
|
lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
quarantine.remove(connection);
|
|
||||||
idle = offerIdle(connection);
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Validated {}", connection);
|
LOG.debug("Validated {}", connection);
|
||||||
|
quarantine.remove(connection);
|
||||||
|
if (!closed)
|
||||||
|
deactivate(connection);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
unlock();
|
unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idle(connection, idle))
|
idle(connection, closed);
|
||||||
proceed();
|
proceed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean cancel()
|
public boolean cancel()
|
||||||
{
|
{
|
||||||
if (latch.compareAndSet(false, true))
|
if (done.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
task.cancel();
|
task.cancel();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -52,6 +52,14 @@ public class Result
|
||||||
this.responseFailure = responseFailure;
|
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
|
* @return the request object
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,16 +18,20 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client.http;
|
package org.eclipse.jetty.client.http;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpChannel;
|
import org.eclipse.jetty.client.HttpChannel;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.HttpReceiver;
|
import org.eclipse.jetty.client.HttpRequest;
|
||||||
import org.eclipse.jetty.client.HttpSender;
|
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.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
|
||||||
public class HttpChannelOverHTTP extends HttpChannel
|
public class HttpChannelOverHTTP extends HttpChannel
|
||||||
|
@ -55,13 +59,13 @@ public class HttpChannelOverHTTP extends HttpChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HttpSender getHttpSender()
|
protected HttpSenderOverHTTP getHttpSender()
|
||||||
{
|
{
|
||||||
return sender;
|
return sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HttpReceiver getHttpReceiver()
|
protected HttpReceiverOverHTTP getHttpReceiver()
|
||||||
{
|
{
|
||||||
return receiver;
|
return receiver;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +89,42 @@ public class HttpChannelOverHTTP extends HttpChannel
|
||||||
connection.release();
|
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()
|
public void receive()
|
||||||
{
|
{
|
||||||
receiver.receive();
|
receiver.receive();
|
||||||
|
@ -131,7 +171,10 @@ public class HttpChannelOverHTTP extends HttpChannel
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
release();
|
if (response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||||
|
connection.remove();
|
||||||
|
else
|
||||||
|
release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,4 +186,5 @@ public class HttpChannelOverHTTP extends HttpChannel
|
||||||
sender,
|
sender,
|
||||||
receiver);
|
receiver);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client.http;
|
package org.eclipse.jetty.client.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.AsynchronousCloseException;
|
import java.nio.channels.AsynchronousCloseException;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
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.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.Sweeper;
|
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 static final Logger LOG = Log.getLogger(HttpConnectionOverHTTP.class);
|
||||||
|
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
private final Promise<Connection> promise;
|
|
||||||
private final AtomicInteger sweeps = new AtomicInteger();
|
private final AtomicInteger sweeps = new AtomicInteger();
|
||||||
|
private final Promise<Connection> promise;
|
||||||
private final Delegate delegate;
|
private final Delegate delegate;
|
||||||
private final HttpChannelOverHTTP channel;
|
private final HttpChannelOverHTTP channel;
|
||||||
private long idleTimeout;
|
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()
|
public void release()
|
||||||
{
|
{
|
||||||
// Restore idle timeout
|
// Restore idle timeout
|
||||||
|
@ -171,6 +179,11 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void remove()
|
||||||
|
{
|
||||||
|
getHttpDestination().remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,12 +16,11 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.start.graph;
|
package org.eclipse.jetty.client.http;
|
||||||
|
|
||||||
/**
|
import org.eclipse.jetty.client.HttpResponse;
|
||||||
* Matcher of Nodes
|
|
||||||
*/
|
public interface HttpConnectionUpgrader
|
||||||
public interface Predicate
|
|
||||||
{
|
{
|
||||||
public boolean match(Node<?> input);
|
public void upgrade(HttpResponse response, HttpConnectionOverHTTP connection);
|
||||||
}
|
}
|
|
@ -22,8 +22,9 @@ import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.Origin;
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.PoolingHttpDestination;
|
import org.eclipse.jetty.client.PoolingHttpDestination;
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
|
||||||
public class HttpDestinationOverHTTP extends PoolingHttpDestination<HttpConnectionOverHTTP>
|
public class HttpDestinationOverHTTP extends PoolingHttpDestination
|
||||||
{
|
{
|
||||||
public HttpDestinationOverHTTP(HttpClient client, Origin origin)
|
public HttpDestinationOverHTTP(HttpClient client, Origin origin)
|
||||||
{
|
{
|
||||||
|
@ -31,8 +32,8 @@ public class HttpDestinationOverHTTP extends PoolingHttpDestination<HttpConnecti
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void send(HttpConnectionOverHTTP connection, HttpExchange exchange)
|
protected void send(Connection connection, HttpExchange exchange)
|
||||||
{
|
{
|
||||||
connection.send(exchange);
|
((HttpConnectionOverHTTP)connection).send(exchange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,17 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
||||||
buffer = null;
|
buffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ByteBuffer onUpgradeFrom()
|
||||||
|
{
|
||||||
|
if (BufferUtil.hasContent(buffer))
|
||||||
|
{
|
||||||
|
ByteBuffer upgradeBuffer = ByteBuffer.allocate(buffer.remaining());
|
||||||
|
upgradeBuffer.put(buffer);
|
||||||
|
return upgradeBuffer;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void process()
|
private void process()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -96,11 +107,13 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
||||||
EndPoint endPoint = connection.getEndPoint();
|
EndPoint endPoint = connection.getEndPoint();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Connection may be closed in a parser callback.
|
boolean upgraded = connection != endPoint.getConnection();
|
||||||
if (connection.isClosed())
|
|
||||||
|
// Connection may be closed or upgraded in a parser callback.
|
||||||
|
if (connection.isClosed() || upgraded)
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("{} closed", connection);
|
LOG.debug("{} {}", connection, upgraded ? "upgraded" : "closed");
|
||||||
releaseBuffer();
|
releaseBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
|
|
||||||
public class HttpSenderOverHTTP extends HttpSender
|
public class HttpSenderOverHTTP extends HttpSender
|
||||||
{
|
{
|
||||||
|
@ -52,77 +53,9 @@ public class HttpSenderOverHTTP extends HttpSender
|
||||||
@Override
|
@Override
|
||||||
protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
|
protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
|
||||||
{
|
{
|
||||||
Request request = exchange.getRequest();
|
|
||||||
ContentProvider requestContent = request.getContent();
|
|
||||||
long contentLength = requestContent == null ? -1 : requestContent.getLength();
|
|
||||||
String path = request.getPath();
|
|
||||||
String query = request.getQuery();
|
|
||||||
if (query != null)
|
|
||||||
path += "?" + query;
|
|
||||||
MetaData.Request requestInfo = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
|
new HeadersCallback(exchange, content, callback).iterate();
|
||||||
ByteBufferPool bufferPool = client.getByteBufferPool();
|
|
||||||
ByteBuffer header = bufferPool.acquire(client.getRequestBufferSize(), false);
|
|
||||||
ByteBuffer chunk = null;
|
|
||||||
|
|
||||||
ByteBuffer contentBuffer = null;
|
|
||||||
boolean lastContent = false;
|
|
||||||
if (!expects100Continue(request))
|
|
||||||
{
|
|
||||||
content.advance();
|
|
||||||
contentBuffer = content.getByteBuffer();
|
|
||||||
lastContent = content.isLast();
|
|
||||||
}
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentBuffer, lastContent);
|
|
||||||
switch (result)
|
|
||||||
{
|
|
||||||
case NEED_CHUNK:
|
|
||||||
{
|
|
||||||
chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FLUSH:
|
|
||||||
{
|
|
||||||
int size = 1;
|
|
||||||
boolean hasChunk = chunk != null;
|
|
||||||
if (hasChunk)
|
|
||||||
++size;
|
|
||||||
boolean hasContent = contentBuffer != null;
|
|
||||||
if (hasContent)
|
|
||||||
++size;
|
|
||||||
ByteBuffer[] toWrite = new ByteBuffer[size];
|
|
||||||
ByteBuffer[] toRecycle = new ByteBuffer[hasChunk ? 2 : 1];
|
|
||||||
toWrite[0] = header;
|
|
||||||
toRecycle[0] = header;
|
|
||||||
if (hasChunk)
|
|
||||||
{
|
|
||||||
toWrite[1] = chunk;
|
|
||||||
toRecycle[1] = chunk;
|
|
||||||
}
|
|
||||||
if (hasContent)
|
|
||||||
toWrite[toWrite.length - 1] = contentBuffer;
|
|
||||||
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
|
|
||||||
endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, toRecycle), toWrite);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case DONE:
|
|
||||||
{
|
|
||||||
// The headers have already been generated, perhaps by a concurrent abort.
|
|
||||||
callback.failed(new HttpRequestException("Could not generate headers", request));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
callback.failed(new IllegalStateException(result.toString()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
|
@ -145,6 +78,10 @@ public class HttpSenderOverHTTP extends HttpSender
|
||||||
ByteBuffer contentBuffer = content.getByteBuffer();
|
ByteBuffer contentBuffer = content.getByteBuffer();
|
||||||
boolean lastContent = content.isLast();
|
boolean lastContent = content.isLast();
|
||||||
HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent);
|
HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Generated content ({} bytes) - {}/{}",
|
||||||
|
contentBuffer == null ? -1 : contentBuffer.remaining(),
|
||||||
|
result, generator);
|
||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case NEED_CHUNK:
|
case NEED_CHUNK:
|
||||||
|
@ -168,17 +105,19 @@ public class HttpSenderOverHTTP extends HttpSender
|
||||||
}
|
}
|
||||||
case CONTINUE:
|
case CONTINUE:
|
||||||
{
|
{
|
||||||
break;
|
if (lastContent)
|
||||||
|
break;
|
||||||
|
callback.succeeded();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
case DONE:
|
case DONE:
|
||||||
{
|
{
|
||||||
assert generator.isEnd();
|
|
||||||
callback.succeeded();
|
callback.succeeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException(result.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -208,6 +147,8 @@ public class HttpSenderOverHTTP extends HttpSender
|
||||||
|
|
||||||
private void shutdownOutput()
|
private void shutdownOutput()
|
||||||
{
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request shutdown output {}", getHttpExchange().getRequest());
|
||||||
getHttpChannel().getHttpConnection().getEndPoint().shutdownOutput();
|
getHttpChannel().getHttpConnection().getEndPoint().shutdownOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,6 +158,148 @@ public class HttpSenderOverHTTP extends HttpSender
|
||||||
return String.format("%s[%s]", super.toString(), generator);
|
return String.format("%s[%s]", super.toString(), generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class HeadersCallback extends IteratingCallback
|
||||||
|
{
|
||||||
|
private final HttpExchange exchange;
|
||||||
|
private final Callback callback;
|
||||||
|
private final MetaData.Request metaData;
|
||||||
|
private ByteBuffer headerBuffer;
|
||||||
|
private ByteBuffer chunkBuffer;
|
||||||
|
private ByteBuffer contentBuffer;
|
||||||
|
private boolean lastContent;
|
||||||
|
private boolean generated;
|
||||||
|
|
||||||
|
public HeadersCallback(HttpExchange exchange, HttpContent content, Callback callback)
|
||||||
|
{
|
||||||
|
super(false);
|
||||||
|
this.exchange = exchange;
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
Request request = exchange.getRequest();
|
||||||
|
ContentProvider requestContent = request.getContent();
|
||||||
|
long contentLength = requestContent == null ? -1 : requestContent.getLength();
|
||||||
|
String path = request.getPath();
|
||||||
|
String query = request.getQuery();
|
||||||
|
if (query != null)
|
||||||
|
path += "?" + query;
|
||||||
|
metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength);
|
||||||
|
|
||||||
|
if (!expects100Continue(request))
|
||||||
|
{
|
||||||
|
content.advance();
|
||||||
|
contentBuffer = content.getByteBuffer();
|
||||||
|
lastContent = content.isLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Action process() throws Exception
|
||||||
|
{
|
||||||
|
HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
|
||||||
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
HttpGenerator.Result result = generator.generateRequest(metaData, headerBuffer, chunkBuffer, contentBuffer, lastContent);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Generated headers ({} bytes), chunk ({} bytes), content ({} bytes) - {}/{}",
|
||||||
|
headerBuffer == null ? -1 : headerBuffer.remaining(),
|
||||||
|
chunkBuffer == null ? -1 : chunkBuffer.remaining(),
|
||||||
|
contentBuffer == null ? -1 : contentBuffer.remaining(),
|
||||||
|
result, generator);
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case NEED_HEADER:
|
||||||
|
{
|
||||||
|
headerBuffer = bufferPool.acquire(client.getRequestBufferSize(), false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NEED_CHUNK:
|
||||||
|
{
|
||||||
|
chunkBuffer = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FLUSH:
|
||||||
|
{
|
||||||
|
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
|
||||||
|
if (chunkBuffer == null)
|
||||||
|
{
|
||||||
|
if (contentBuffer == null)
|
||||||
|
endPoint.write(this, headerBuffer);
|
||||||
|
else
|
||||||
|
endPoint.write(this, headerBuffer, contentBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (contentBuffer == null)
|
||||||
|
endPoint.write(this, headerBuffer, chunkBuffer);
|
||||||
|
else
|
||||||
|
endPoint.write(this, headerBuffer, chunkBuffer, contentBuffer);
|
||||||
|
}
|
||||||
|
generated = true;
|
||||||
|
return Action.SCHEDULED;
|
||||||
|
}
|
||||||
|
case SHUTDOWN_OUT:
|
||||||
|
{
|
||||||
|
shutdownOutput();
|
||||||
|
return Action.SUCCEEDED;
|
||||||
|
}
|
||||||
|
case CONTINUE:
|
||||||
|
{
|
||||||
|
if (generated)
|
||||||
|
return Action.SUCCEEDED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DONE:
|
||||||
|
{
|
||||||
|
if (generated)
|
||||||
|
return Action.SUCCEEDED;
|
||||||
|
// The headers have already been generated by some
|
||||||
|
// other thread, perhaps by a concurrent abort().
|
||||||
|
throw new HttpRequestException("Could not generate headers", exchange.getRequest());
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException(result.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
super.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
release();
|
||||||
|
callback.failed(x);
|
||||||
|
super.failed(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCompleteSuccess()
|
||||||
|
{
|
||||||
|
super.onCompleteSuccess();
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void release()
|
||||||
|
{
|
||||||
|
HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
|
||||||
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
|
bufferPool.release(headerBuffer);
|
||||||
|
headerBuffer = null;
|
||||||
|
if (chunkBuffer != null)
|
||||||
|
bufferPool.release(chunkBuffer);
|
||||||
|
chunkBuffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ByteBufferRecyclerCallback implements Callback
|
private class ByteBufferRecyclerCallback implements Callback
|
||||||
{
|
{
|
||||||
private final Callback callback;
|
private final Callback callback;
|
||||||
|
|
|
@ -0,0 +1,404 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.AsyncContentProvider;
|
||||||
|
import org.eclipse.jetty.client.Synchronizable;
|
||||||
|
import org.eclipse.jetty.client.api.ContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.io.RuntimeIOException;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A {@link ContentProvider} for form uploads with the {@code "multipart/form-data"}
|
||||||
|
* content type.</p>
|
||||||
|
* <p>Example usage:</p>
|
||||||
|
* <pre>
|
||||||
|
* MultiPartContentProvider multiPart = new MultiPartContentProvider();
|
||||||
|
* multiPart.addFieldPart("field", new StringContentProvider("foo"), null);
|
||||||
|
* multiPart.addFilePart("icon", "img.png", new PathContentProvider(Paths.get("/tmp/img.png")), null);
|
||||||
|
* multiPart.close();
|
||||||
|
* ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
* .method(HttpMethod.POST)
|
||||||
|
* .content(multiPart)
|
||||||
|
* .send();
|
||||||
|
* </pre>
|
||||||
|
* <p>The above example would be the equivalent of submitting this form:</p>
|
||||||
|
* <pre>
|
||||||
|
* <form method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||||
|
* <input type="text" name="field" value="foo" />
|
||||||
|
* <input type="file" name="icon" />
|
||||||
|
* </form>
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
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<Part> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Adds a field part with the given {@code name} as field name, and the given
|
||||||
|
* {@code content} as part content.</p>
|
||||||
|
* <p>The {@code Content-Type} of this part will be obtained from:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>the {@code Content-Type} header in the {@code fields} parameter; otherwise</li>
|
||||||
|
* <li>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</li>
|
||||||
|
* <li>"text/plain"</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>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.</p>
|
||||||
|
* <p>The {@code Content-Type} of this part will be obtained from:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>the {@code Content-Type} header in the {@code fields} parameter; otherwise</li>
|
||||||
|
* <li>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</li>
|
||||||
|
* <li>"application/octet-stream"</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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<ByteBuffer> 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<ByteBuffer>, Synchronizable, Callback, Closeable
|
||||||
|
{
|
||||||
|
private Iterator<ByteBuffer> 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,6 @@ import java.util.Collection;
|
||||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.NetworkConnector;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
|
@ -51,7 +50,7 @@ public abstract class AbstractHttpClientServerTest
|
||||||
protected String scheme;
|
protected String scheme;
|
||||||
protected Server server;
|
protected Server server;
|
||||||
protected HttpClient client;
|
protected HttpClient client;
|
||||||
protected NetworkConnector connector;
|
protected ServerConnector connector;
|
||||||
|
|
||||||
public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
|
public AbstractHttpClientServerTest(SslContextFactory sslContextFactory)
|
||||||
{
|
{
|
||||||
|
@ -60,6 +59,12 @@ public abstract class AbstractHttpClientServerTest
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(Handler handler) throws Exception
|
public void start(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
startServer(handler);
|
||||||
|
startClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void startServer(Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
if (sslContextFactory != null)
|
if (sslContextFactory != null)
|
||||||
{
|
{
|
||||||
|
@ -80,8 +85,6 @@ public abstract class AbstractHttpClientServerTest
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
server.setHandler(handler);
|
server.setHandler(handler);
|
||||||
server.start();
|
server.start();
|
||||||
|
|
||||||
startClient();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void startClient() throws Exception
|
protected void startClient() throws Exception
|
||||||
|
|
|
@ -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<EndPoint> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 ClientConnectionFactory connectionFactory;
|
||||||
private final Map<String, Object> context;
|
private final Map<String, Object> context;
|
||||||
|
@ -162,8 +162,19 @@ public class HttpClientCustomProxyTest
|
||||||
public void onOpen()
|
public void onOpen()
|
||||||
{
|
{
|
||||||
super.onOpen();
|
super.onOpen();
|
||||||
|
getEndPoint().write(this, ByteBuffer.wrap(CAFE_BABE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
fillInterested();
|
fillInterested();
|
||||||
getEndPoint().write(new Callback.Adapter(), ByteBuffer.wrap(CAFE_BABE));
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -177,7 +188,7 @@ public class HttpClientCustomProxyTest
|
||||||
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||||
|
|
||||||
// We are good, upgrade the connection
|
// We are good, upgrade the connection
|
||||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(getEndPoint(), context));
|
getEndPoint().upgrade(connectionFactory.newConnection(getEndPoint(), context));
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
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;
|
private final org.eclipse.jetty.server.ConnectionFactory connectionFactory;
|
||||||
|
|
||||||
|
@ -232,15 +243,25 @@ public class HttpClientCustomProxyTest
|
||||||
int filled = getEndPoint().fill(buffer);
|
int filled = getEndPoint().fill(buffer);
|
||||||
Assert.assertEquals(4, filled);
|
Assert.assertEquals(4, filled);
|
||||||
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
Assert.assertArrayEquals(CAFE_BABE, buffer.array());
|
||||||
getEndPoint().write(new Callback.Adapter(), buffer);
|
getEndPoint().write(this, buffer);
|
||||||
|
|
||||||
// We are good, upgrade the connection
|
|
||||||
ClientConnectionFactory.Helper.replaceConnection(this, connectionFactory.newConnection(connector, getEndPoint()));
|
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
// We are good, upgrade the connection
|
||||||
|
getEndPoint().upgrade(connectionFactory.newConnection(connector, getEndPoint()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
||||||
ConnectionPool connectionPool = httpDestination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)httpDestination.getConnectionPool();
|
||||||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
||||||
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
|
Assert.assertFalse(httpConnection.getEndPoint().isOpen());
|
||||||
|
|
||||||
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
HttpDestinationOverHTTP httpDestination = (HttpDestinationOverHTTP)destination;
|
||||||
ConnectionPool connectionPool = httpDestination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)httpDestination.getConnectionPool();
|
||||||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
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.HttpClientTransportOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
||||||
|
@ -89,14 +86,7 @@ public class HttpClientFailureTest
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.onRequestHeaders(new Request.HeadersListener()
|
.onRequestHeaders(request -> connectionRef.get().getEndPoint().close())
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onHeaders(Request request)
|
|
||||||
{
|
|
||||||
connectionRef.get().getEndPoint().close();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
Assert.fail();
|
Assert.fail();
|
||||||
|
@ -106,7 +96,7 @@ public class HttpClientFailureTest
|
||||||
// Expected.
|
// Expected.
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionPool connectionPool = connectionRef.get().getHttpDestination().getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)connectionRef.get().getHttpDestination().getConnectionPool();
|
||||||
Assert.assertEquals(0, connectionPool.getConnectionCount());
|
Assert.assertEquals(0, connectionPool.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
||||||
|
@ -134,25 +124,17 @@ public class HttpClientFailureTest
|
||||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||||
DeferredContentProvider content = new DeferredContentProvider();
|
DeferredContentProvider content = new DeferredContentProvider();
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.onRequestCommit(new Request.CommitListener()
|
.onRequestCommit(request ->
|
||||||
{
|
{
|
||||||
@Override
|
connectionRef.get().getEndPoint().close();
|
||||||
public void onCommit(Request request)
|
commitLatch.countDown();
|
||||||
{
|
|
||||||
connectionRef.get().getEndPoint().close();
|
|
||||||
commitLatch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.content(content)
|
.content(content)
|
||||||
.idleTimeout(2, TimeUnit.SECONDS)
|
.idleTimeout(2, TimeUnit.SECONDS)
|
||||||
.send(new Response.CompleteListener()
|
.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
if (result.isFailed())
|
||||||
public void onComplete(Result result)
|
completeLatch.countDown();
|
||||||
{
|
|
||||||
if (result.isFailed())
|
|
||||||
completeLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(commitLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(commitLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
@ -170,7 +152,7 @@ public class HttpClientFailureTest
|
||||||
Assert.assertTrue(contentLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(contentLatch.await(5, TimeUnit.SECONDS));
|
||||||
Assert.assertTrue(completeLatch.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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
||||||
|
|
|
@ -47,6 +47,7 @@ import java.util.concurrent.Exchanger;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
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.HttpConnectionOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
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.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
@ -111,7 +114,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
Assert.assertEquals(200, response.getStatus());
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
HttpConnectionOverHTTP connection = null;
|
HttpConnectionOverHTTP connection = null;
|
||||||
|
@ -367,16 +370,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
final byte[] content = {0, 1, 2, 3};
|
final byte[] content = {0, 1, 2, 3};
|
||||||
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
||||||
.onRequestContent(new Request.ContentListener()
|
.onRequestContent((request, buffer) ->
|
||||||
{
|
{
|
||||||
@Override
|
byte[] bytes = new byte[buffer.remaining()];
|
||||||
public void onContent(Request request, ByteBuffer buffer)
|
buffer.get(bytes);
|
||||||
{
|
if (!Arrays.equals(content, bytes))
|
||||||
byte[] bytes = new byte[buffer.remaining()];
|
request.abort(new Exception());
|
||||||
buffer.get(bytes);
|
|
||||||
if (!Arrays.equals(content, bytes))
|
|
||||||
request.abort(new Exception());
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.content(new BytesContentProvider(content))
|
.content(new BytesContentProvider(content))
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
@ -401,16 +400,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
final AtomicInteger progress = new AtomicInteger();
|
final AtomicInteger progress = new AtomicInteger();
|
||||||
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
||||||
.onRequestContent(new Request.ContentListener()
|
.onRequestContent((request, buffer) ->
|
||||||
{
|
{
|
||||||
@Override
|
byte[] bytes = new byte[buffer.remaining()];
|
||||||
public void onContent(Request request, ByteBuffer buffer)
|
Assert.assertEquals(1, bytes.length);
|
||||||
{
|
buffer.get(bytes);
|
||||||
byte[] bytes = new byte[buffer.remaining()];
|
Assert.assertEquals(bytes[0], progress.getAndIncrement());
|
||||||
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}))
|
.content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
@ -432,19 +427,15 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
final CountDownLatch successLatch = new CountDownLatch(2);
|
final CountDownLatch successLatch = new CountDownLatch(2);
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestBegin(new Request.BeginListener()
|
.onRequestBegin(request ->
|
||||||
{
|
{
|
||||||
@Override
|
try
|
||||||
public void onBegin(Request request)
|
|
||||||
{
|
{
|
||||||
try
|
latch.await();
|
||||||
{
|
}
|
||||||
latch.await();
|
catch (InterruptedException x)
|
||||||
}
|
{
|
||||||
catch (InterruptedException x)
|
x.printStackTrace();
|
||||||
{
|
|
||||||
x.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.send(new Response.Listener.Adapter()
|
.send(new Response.Listener.Adapter()
|
||||||
|
@ -459,14 +450,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestQueued(new Request.QueuedListener()
|
.onRequestQueued(request -> latch.countDown())
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onQueued(Request request)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.send(new Response.Listener.Adapter()
|
.send(new Response.Listener.Adapter()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -514,27 +498,16 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.onResponseFailure(new Response.FailureListener()
|
.onResponseFailure((response, failure) -> latch.countDown())
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onFailure(Response response, Throwable failure)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.send(null);
|
.send(null);
|
||||||
|
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.path("/two")
|
.path("/two")
|
||||||
.onResponseSuccess(new Response.SuccessListener()
|
.onResponseSuccess(response ->
|
||||||
{
|
{
|
||||||
@Override
|
Assert.assertEquals(200, response.getStatus());
|
||||||
public void onSuccess(Response response)
|
latch.countDown();
|
||||||
{
|
|
||||||
Assert.assertEquals(200, response.getStatus());
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.send(null);
|
.send(null);
|
||||||
|
|
||||||
|
@ -564,14 +537,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.file(file)
|
.file(file)
|
||||||
.onRequestSuccess(new Request.SuccessListener()
|
.onRequestSuccess(request ->
|
||||||
{
|
{
|
||||||
@Override
|
requestTime.set(System.nanoTime());
|
||||||
public void onSuccess(Request request)
|
latch.countDown();
|
||||||
{
|
|
||||||
requestTime.set(System.nanoTime());
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.send(new Response.Listener.Adapter()
|
.send(new Response.Listener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -674,14 +643,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
final int port = connector.getLocalPort();
|
final int port = connector.getLocalPort();
|
||||||
client.newRequest(host, port)
|
client.newRequest(host, port)
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestBegin(new Request.BeginListener()
|
.onRequestBegin(request ->
|
||||||
{
|
{
|
||||||
@Override
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
public void onBegin(Request request)
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
{
|
connectionPool.getActiveConnections().iterator().next().close();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
|
||||||
destination.getConnectionPool().getActiveConnections().peek().close();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.send(new Response.Listener.Adapter()
|
.send(new Response.Listener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -773,14 +739,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onResponseHeader(new Response.HeaderListener()
|
.onResponseHeader((response1, field) -> !field.getName().equals(headerName))
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean onHeader(Response response, HttpField field)
|
|
||||||
{
|
|
||||||
return !field.getName().equals(headerName);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
@ -864,16 +823,12 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
client.newRequest("idontexist", 80)
|
client.newRequest("idontexist", 80)
|
||||||
.send(new Response.CompleteListener()
|
.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
Assert.assertTrue(result.isFailed());
|
||||||
public void onComplete(Result result)
|
Throwable failure = result.getFailure();
|
||||||
{
|
Assert.assertTrue(failure instanceof UnknownHostException);
|
||||||
Assert.assertTrue(result.isFailed());
|
latch.countDown();
|
||||||
Throwable failure = result.getFailure();
|
|
||||||
Assert.assertTrue(failure instanceof UnknownHostException);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
|
Assert.assertTrue(latch.await(10, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
@ -1323,14 +1278,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.send(new Response.CompleteListener()
|
.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
if (result.isFailed())
|
||||||
public void onComplete(Result result)
|
completeLatch.countDown();
|
||||||
{
|
|
||||||
if (result.isFailed())
|
|
||||||
completeLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
|
@ -1485,6 +1436,54 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
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<Connection> 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
|
@Test
|
||||||
public void testCONNECTWithHTTP10() throws Exception
|
public void testCONNECTWithHTTP10() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -448,7 +448,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
||||||
start(new EmptyServerHandler());
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
long timeout = 1000;
|
long timeout = 1000;
|
||||||
Request request = client.newRequest("badscheme://localhost:" + connector.getLocalPort());
|
Request request = client.newRequest("badscheme://localhost:badport");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,8 +31,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
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.HttpChannelOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
|
@ -121,14 +119,7 @@ public class HttpClientUploadDuringServerShutdown
|
||||||
int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024);
|
int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024);
|
||||||
client.newRequest("localhost", 8888)
|
client.newRequest("localhost", 8888)
|
||||||
.content(new BytesContentProvider(new byte[length]))
|
.content(new BytesContentProvider(new byte[length]))
|
||||||
.send(new Response.CompleteListener()
|
.send(result -> latch.countDown());
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onComplete(Result result)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
long sleep = 1 + random.nextInt(10);
|
long sleep = 1 + random.nextInt(10);
|
||||||
TimeUnit.MILLISECONDS.sleep(sleep);
|
TimeUnit.MILLISECONDS.sleep(sleep);
|
||||||
}
|
}
|
||||||
|
@ -244,35 +235,24 @@ public class HttpClientUploadDuringServerShutdown
|
||||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.timeout(10, TimeUnit.SECONDS)
|
.timeout(10, TimeUnit.SECONDS)
|
||||||
.onRequestBegin(new org.eclipse.jetty.client.api.Request.BeginListener()
|
.onRequestBegin(request ->
|
||||||
{
|
{
|
||||||
@Override
|
try
|
||||||
public void onBegin(org.eclipse.jetty.client.api.Request request)
|
|
||||||
{
|
{
|
||||||
try
|
beginLatch.countDown();
|
||||||
{
|
completeLatch.await(5, TimeUnit.SECONDS);
|
||||||
beginLatch.countDown();
|
}
|
||||||
completeLatch.await(5, TimeUnit.SECONDS);
|
catch (InterruptedException x)
|
||||||
}
|
{
|
||||||
catch (InterruptedException x)
|
x.printStackTrace();
|
||||||
{
|
|
||||||
x.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.send(new Response.CompleteListener()
|
.send(result -> completeLatch.countDown());
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onComplete(Result result)
|
|
||||||
{
|
|
||||||
completeLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination("http", "localhost", connector.getLocalPort());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, pool.getIdleConnections().size());
|
Assert.assertEquals(0, pool.getIdleConnections().size());
|
||||||
Assert.assertEquals(0, pool.getActiveConnections().size());
|
Assert.assertEquals(0, pool.getActiveConnections().size());
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -69,35 +70,24 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
final CountDownLatch headersLatch = new CountDownLatch(1);
|
final CountDownLatch headersLatch = new CountDownLatch(1);
|
||||||
final CountDownLatch successLatch = new CountDownLatch(3);
|
final CountDownLatch successLatch = new CountDownLatch(3);
|
||||||
client.newRequest(host, port)
|
client.newRequest(host, port)
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestSuccess(new Request.SuccessListener()
|
.onRequestSuccess(request -> successLatch.countDown())
|
||||||
|
.onResponseHeaders(response ->
|
||||||
{
|
{
|
||||||
@Override
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
public void onSuccess(Request request)
|
Assert.assertEquals(1, activeConnections.size());
|
||||||
{
|
headersLatch.countDown();
|
||||||
successLatch.countDown();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onResponseHeaders(new Response.HeadersListener()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onHeaders(Response response)
|
|
||||||
{
|
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
|
||||||
Assert.assertEquals(1, activeConnections.size());
|
|
||||||
headersLatch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.send(new Response.Listener.Adapter()
|
.send(new Response.Listener.Adapter()
|
||||||
{
|
{
|
||||||
|
@ -130,12 +120,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
final CountDownLatch beginLatch = new CountDownLatch(1);
|
final CountDownLatch beginLatch = new CountDownLatch(1);
|
||||||
|
@ -145,7 +135,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
@Override
|
@Override
|
||||||
public void onBegin(Request request)
|
public void onBegin(Request request)
|
||||||
{
|
{
|
||||||
activeConnections.peek().close();
|
activeConnections.iterator().next().close();
|
||||||
beginLatch.countDown();
|
beginLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,12 +171,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
final CountDownLatch successLatch = new CountDownLatch(3);
|
final CountDownLatch successLatch = new CountDownLatch(3);
|
||||||
|
@ -241,12 +231,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
final long delay = 1000;
|
final long delay = 1000;
|
||||||
|
@ -314,12 +304,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
server.stop();
|
server.stop();
|
||||||
|
@ -327,22 +317,11 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
final CountDownLatch failureLatch = new CountDownLatch(2);
|
final CountDownLatch failureLatch = new CountDownLatch(2);
|
||||||
client.newRequest(host, port)
|
client.newRequest(host, port)
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestFailure(new Request.FailureListener()
|
.onRequestFailure((request, failure) -> failureLatch.countDown())
|
||||||
|
.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
Assert.assertTrue(result.isFailed());
|
||||||
public void onFailure(Request request, Throwable failure)
|
failureLatch.countDown();
|
||||||
{
|
|
||||||
failureLatch.countDown();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.send(new Response.Listener.Adapter()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void onComplete(Result result)
|
|
||||||
{
|
|
||||||
Assert.assertTrue(result.isFailed());
|
|
||||||
failureLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
@ -367,12 +346,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
@ -417,12 +396,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
|
Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
|
||||||
|
@ -467,12 +446,12 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
ContentResponse response = client.newRequest(host, port)
|
ContentResponse response = client.newRequest(host, port)
|
||||||
|
@ -499,25 +478,21 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
||||||
String host = "localhost";
|
String host = "localhost";
|
||||||
int port = connector.getLocalPort();
|
int port = connector.getLocalPort();
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port);
|
||||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||||
|
|
||||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Assert.assertEquals(0, idleConnections.size());
|
Assert.assertEquals(0, idleConnections.size());
|
||||||
|
|
||||||
final Queue<Connection> activeConnections = connectionPool.getActiveConnections();
|
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||||
Assert.assertEquals(0, activeConnections.size());
|
Assert.assertEquals(0, activeConnections.size());
|
||||||
|
|
||||||
client.setStrictEventOrdering(false);
|
client.setStrictEventOrdering(false);
|
||||||
ContentResponse response = client.newRequest(host, port)
|
ContentResponse response = client.newRequest(host, port)
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onResponseBegin(new Response.BeginListener()
|
.onResponseBegin(response1 ->
|
||||||
{
|
{
|
||||||
@Override
|
// Simulate a HTTP 1.0 response has been received.
|
||||||
public void onBegin(Response response)
|
((HttpResponse)response1).version(HttpVersion.HTTP_1_0);
|
||||||
{
|
|
||||||
// Simulate a HTTP 1.0 response has been received.
|
|
||||||
((HttpResponse)response).version(HttpVersion.HTTP_1_0);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
|
|
@ -25,12 +25,12 @@ import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Request;
|
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.api.Result;
|
||||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||||
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
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());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().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());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().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());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
||||||
|
@ -204,14 +204,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
||||||
{
|
{
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestCommit(new Request.CommitListener()
|
.onRequestCommit(request ->
|
||||||
{
|
{
|
||||||
@Override
|
aborted.set(request.abort(cause));
|
||||||
public void onCommit(Request request)
|
latch.countDown();
|
||||||
{
|
|
||||||
aborted.set(request.abort(cause));
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.timeout(5, TimeUnit.SECONDS)
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
.send();
|
.send();
|
||||||
|
@ -225,7 +221,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
||||||
|
@ -260,14 +256,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
||||||
{
|
{
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestCommit(new Request.CommitListener()
|
.onRequestCommit(request ->
|
||||||
{
|
{
|
||||||
@Override
|
aborted.set(request.abort(cause));
|
||||||
public void onCommit(Request request)
|
latch.countDown();
|
||||||
{
|
|
||||||
aborted.set(request.abort(cause));
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
|
.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());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
||||||
|
@ -315,14 +307,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
||||||
{
|
{
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.onRequestContent(new Request.ContentListener()
|
.onRequestContent((request, content) ->
|
||||||
{
|
{
|
||||||
@Override
|
aborted.set(request.abort(cause));
|
||||||
public void onContent(Request request, ByteBuffer content)
|
latch.countDown();
|
||||||
{
|
|
||||||
aborted.set(request.abort(cause));
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
|
.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());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().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());
|
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.getConnectionCount());
|
||||||
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
|
||||||
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
Assert.assertEquals(0, connectionPool.getIdleConnections().size());
|
||||||
|
@ -486,15 +474,11 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
||||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.timeout(3 * delay, TimeUnit.MILLISECONDS);
|
.timeout(3 * delay, TimeUnit.MILLISECONDS);
|
||||||
request.send(new Response.CompleteListener()
|
request.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
Assert.assertTrue(result.isFailed());
|
||||||
public void onComplete(Result result)
|
Assert.assertSame(cause, result.getFailure());
|
||||||
{
|
latch.countDown();
|
||||||
Assert.assertTrue(result.isFailed());
|
|
||||||
Assert.assertSame(cause, result.getFailure());
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TimeUnit.MILLISECONDS.sleep(delay);
|
TimeUnit.MILLISECONDS.sleep(delay);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -194,7 +194,7 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest
|
||||||
return new HttpDestinationOverHTTP(getHttpClient(), origin)
|
return new HttpDestinationOverHTTP(getHttpClient(), origin)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected ConnectionPool newConnectionPool(HttpClient client)
|
protected DuplexConnectionPool newConnectionPool(HttpClient client)
|
||||||
{
|
{
|
||||||
return new ValidatingConnectionPool(this, client.getMaxConnectionsPerDestination(), this, client.getScheduler(), timeout);
|
return new ValidatingConnectionPool(this, client.getMaxConnectionsPerDestination(), this, client.getScheduler(), timeout);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,15 +25,13 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
|
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
|
||||||
import org.eclipse.jetty.client.ConnectionPool;
|
import org.eclipse.jetty.client.ConnectionPool;
|
||||||
|
import org.eclipse.jetty.client.DuplexConnectionPool;
|
||||||
import org.eclipse.jetty.client.EmptyServerHandler;
|
import org.eclipse.jetty.client.EmptyServerHandler;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.Origin;
|
import org.eclipse.jetty.client.Origin;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Destination;
|
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.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
@ -59,11 +57,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
public void test_FirstAcquire_WithEmptyQueue() throws Exception
|
public void test_FirstAcquire_WithEmptyQueue() throws Exception
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
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)
|
if (connection == null)
|
||||||
{
|
{
|
||||||
// There are no queued requests, so the newly created connection will be idle
|
// 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);
|
Assert.assertNotNull(connection);
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
|
public void test_SecondAcquire_AfterFirstAcquire_WithEmptyQueue_ReturnsSameConnection() throws Exception
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
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)
|
if (connection1 == null)
|
||||||
{
|
{
|
||||||
// There are no queued requests, so the newly created connection will be idle
|
// 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)
|
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||||
{
|
{
|
||||||
TimeUnit.MILLISECONDS.sleep(50);
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
connection1 = destination.getConnectionPool().getIdleConnections().peek();
|
connection1 = connectionPool.getIdleConnections().peek();
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection1);
|
Assert.assertNotNull(connection1);
|
||||||
|
|
||||||
Connection connection2 = destination.acquire();
|
Connection connection2 = connectionPool.acquire();
|
||||||
Assert.assertSame(connection1, connection2);
|
Assert.assertSame(connection1, connection2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,16 +101,16 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
@Override
|
@Override
|
||||||
protected ConnectionPool newConnectionPool(HttpClient client)
|
protected ConnectionPool newConnectionPool(HttpClient client)
|
||||||
{
|
{
|
||||||
return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
|
return new DuplexConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void idleCreated(Connection connection)
|
protected void onCreated(Connection connection)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
idleLatch.countDown();
|
idleLatch.countDown();
|
||||||
latch.await(5, TimeUnit.SECONDS);
|
latch.await(5, TimeUnit.SECONDS);
|
||||||
super.idleCreated(connection);
|
super.onCreated(connection);
|
||||||
}
|
}
|
||||||
catch (InterruptedException x)
|
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().
|
// Make sure we entered idleCreated().
|
||||||
Assert.assertTrue(idleLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(idleLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
@ -128,13 +132,13 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
Assert.assertNull(connection1);
|
Assert.assertNull(connection1);
|
||||||
|
|
||||||
// Second attempt also returns null because we delayed idleCreated() above.
|
// Second attempt also returns null because we delayed idleCreated() above.
|
||||||
Connection connection2 = destination.acquire();
|
Connection connection2 = connectionPool.acquire();
|
||||||
Assert.assertNull(connection2);
|
Assert.assertNull(connection2);
|
||||||
|
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
|
|
||||||
// There must be 2 idle connections.
|
// There must be 2 idle connections.
|
||||||
Queue<Connection> idleConnections = destination.getConnectionPool().getIdleConnections();
|
Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||||
Connection connection = timedPoll(idleConnections, 5, TimeUnit.SECONDS);
|
Connection connection = timedPoll(idleConnections, 5, TimeUnit.SECONDS);
|
||||||
Assert.assertNotNull(connection);
|
Assert.assertNotNull(connection);
|
||||||
connection = timedPoll(idleConnections, 5, TimeUnit.SECONDS);
|
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
|
public void test_Acquire_Process_Release_Acquire_ReturnsSameConnection() throws Exception
|
||||||
{
|
{
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
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();
|
long start = System.nanoTime();
|
||||||
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||||
{
|
{
|
||||||
TimeUnit.MILLISECONDS.sleep(50);
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
connection1 = (HttpConnectionOverHTTP)destination.getConnectionPool().getIdleConnections().peek();
|
connection1 = (HttpConnectionOverHTTP)connectionPool.getIdleConnections().peek();
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection1);
|
Assert.assertNotNull(connection1);
|
||||||
|
|
||||||
// Acquire the connection to make it active
|
// Acquire the connection to make it active
|
||||||
Assert.assertSame(connection1, destination.acquire());
|
Assert.assertSame(connection1, connectionPool.acquire());
|
||||||
|
|
||||||
destination.process(connection1);
|
destination.process(connection1);
|
||||||
destination.release(connection1);
|
destination.release(connection1);
|
||||||
|
|
||||||
Connection connection2 = destination.acquire();
|
Connection connection2 = connectionPool.acquire();
|
||||||
Assert.assertSame(connection1, connection2);
|
Assert.assertSame(connection1, connection2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +178,9 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
client.setIdleTimeout(idleTimeout);
|
client.setIdleTimeout(idleTimeout);
|
||||||
|
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort()));
|
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)
|
if (connection1 == null)
|
||||||
{
|
{
|
||||||
// There are no queued requests, so the newly created connection will be idle
|
// 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)
|
while (connection1 == null && TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) < 5)
|
||||||
{
|
{
|
||||||
TimeUnit.MILLISECONDS.sleep(50);
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
connection1 = destination.getConnectionPool().getIdleConnections().peek();
|
connection1 = connectionPool.getIdleConnections().peek();
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(connection1);
|
Assert.assertNotNull(connection1);
|
||||||
|
|
||||||
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
||||||
|
|
||||||
connection1 = destination.getConnectionPool().getIdleConnections().poll();
|
connection1 = connectionPool.getIdleConnections().poll();
|
||||||
Assert.assertNull(connection1);
|
Assert.assertNull(connection1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,35 +218,23 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.path("/one")
|
.path("/one")
|
||||||
.onRequestQueued(new Request.QueuedListener()
|
.onRequestQueued(request ->
|
||||||
{
|
{
|
||||||
@Override
|
// This request exceeds the maximum queued, should fail
|
||||||
public void onQueued(Request request)
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
{
|
.scheme(scheme)
|
||||||
// This request exceeds the maximum queued, should fail
|
.path("/two")
|
||||||
client.newRequest("localhost", connector.getLocalPort())
|
.send(result ->
|
||||||
.scheme(scheme)
|
{
|
||||||
.path("/two")
|
Assert.assertTrue(result.isFailed());
|
||||||
.send(new Response.CompleteListener()
|
Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
|
||||||
{
|
failureLatch.countDown();
|
||||||
@Override
|
});
|
||||||
public void onComplete(Result result)
|
|
||||||
{
|
|
||||||
Assert.assertTrue(result.isFailed());
|
|
||||||
Assert.assertThat(result.getRequestFailure(), Matchers.instanceOf(RejectedExecutionException.class));
|
|
||||||
failureLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.send(new Response.CompleteListener()
|
.send(result ->
|
||||||
{
|
{
|
||||||
@Override
|
if (result.isSucceeded())
|
||||||
public void onComplete(Result result)
|
successLatch.countDown();
|
||||||
{
|
|
||||||
if (result.isSucceeded())
|
|
||||||
successLatch.countDown();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
|
@ -61,8 +61,10 @@ public class HttpReceiverOverHTTPTest
|
||||||
client = new HttpClient();
|
client = new HttpClient();
|
||||||
client.start();
|
client.start();
|
||||||
destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
endPoint = new ByteArrayEndPoint();
|
endPoint = new ByteArrayEndPoint();
|
||||||
connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<>());
|
||||||
|
endPoint.setConnection(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -207,7 +209,7 @@ public class HttpReceiverOverHTTPTest
|
||||||
@Test
|
@Test
|
||||||
public void test_FillInterested_RacingWith_BufferRelease() throws Exception
|
public void test_FillInterested_RacingWith_BufferRelease() throws Exception
|
||||||
{
|
{
|
||||||
connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>())
|
connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<>())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected HttpChannelOverHTTP newHttpChannel()
|
protected HttpChannelOverHTTP newHttpChannel()
|
||||||
|
@ -234,6 +236,7 @@ public class HttpReceiverOverHTTPTest
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
endPoint.setConnection(connection);
|
||||||
|
|
||||||
// Partial response to trigger the call to fillInterested().
|
// Partial response to trigger the call to fillInterested().
|
||||||
endPoint.addInput("" +
|
endPoint.addInput("" +
|
||||||
|
|
|
@ -67,6 +67,7 @@ public class HttpSenderOverHTTPTest
|
||||||
{
|
{
|
||||||
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
final CountDownLatch headersLatch = new CountDownLatch(1);
|
final CountDownLatch headersLatch = new CountDownLatch(1);
|
||||||
|
@ -100,6 +101,7 @@ public class HttpSenderOverHTTPTest
|
||||||
{
|
{
|
||||||
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
|
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
connection.send(request, null);
|
connection.send(request, null);
|
||||||
|
@ -129,6 +131,7 @@ public class HttpSenderOverHTTPTest
|
||||||
// Shutdown output to trigger the exception on write
|
// Shutdown output to trigger the exception on write
|
||||||
endPoint.shutdownOutput();
|
endPoint.shutdownOutput();
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
final CountDownLatch failureLatch = new CountDownLatch(2);
|
final CountDownLatch failureLatch = new CountDownLatch(2);
|
||||||
|
@ -158,6 +161,7 @@ public class HttpSenderOverHTTPTest
|
||||||
{
|
{
|
||||||
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
|
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
final CountDownLatch failureLatch = new CountDownLatch(2);
|
final CountDownLatch failureLatch = new CountDownLatch(2);
|
||||||
|
@ -193,6 +197,7 @@ public class HttpSenderOverHTTPTest
|
||||||
{
|
{
|
||||||
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
String content = "abcdef";
|
String content = "abcdef";
|
||||||
|
@ -227,6 +232,7 @@ public class HttpSenderOverHTTPTest
|
||||||
{
|
{
|
||||||
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
String content1 = "0123456789";
|
String content1 = "0123456789";
|
||||||
|
@ -262,6 +268,7 @@ public class HttpSenderOverHTTPTest
|
||||||
{
|
{
|
||||||
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
|
||||||
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
|
||||||
|
destination.start();
|
||||||
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
|
||||||
Request request = client.newRequest(URI.create("http://localhost/"));
|
Request request = client.newRequest(URI.create("http://localhost/"));
|
||||||
String content1 = "0123456789";
|
String content1 = "0123456789";
|
||||||
|
|
|
@ -54,10 +54,10 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
|
import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
|
||||||
import org.eclipse.jetty.http.HttpParser;
|
import org.eclipse.jetty.http.HttpParser;
|
||||||
|
import org.eclipse.jetty.io.ChannelEndPoint;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.ManagedSelector;
|
import org.eclipse.jetty.io.ManagedSelector;
|
||||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.HttpConnection;
|
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)
|
ServerConnector connector = new ServerConnector(server, null,null,null,1,1,sslFactory, httpFactory)
|
||||||
{
|
{
|
||||||
@Override
|
@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);
|
serverEndPoint.set(endp);
|
||||||
return endp;
|
return endp;
|
||||||
}
|
}
|
||||||
|
@ -367,11 +367,19 @@ public class SslBytesServerTest extends SslBytesTest
|
||||||
System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
|
System.arraycopy(doneBytes, 0, chunk, recordBytes.length, doneBytes.length);
|
||||||
System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
|
System.arraycopy(closeRecordBytes, 0, chunk, recordBytes.length + doneBytes.length, closeRecordBytes.length);
|
||||||
proxy.flushToServer(0, chunk);
|
proxy.flushToServer(0, chunk);
|
||||||
|
|
||||||
// Close the raw socket
|
// Close the raw socket
|
||||||
proxy.flushToServer(null);
|
proxy.flushToServer(null);
|
||||||
|
|
||||||
// Expect the server to send a FIN as well
|
// Expect the server to send a FIN as well
|
||||||
record = proxy.readFromServer();
|
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);
|
Assert.assertNull(record);
|
||||||
|
|
||||||
// Check that we did not spin
|
// Check that we did not spin
|
||||||
|
|
|
@ -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<Part> 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<Part> 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<Part> 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<Part> 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<Part> 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<Part> 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<Part> 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<Part> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
#org.eclipse.jetty.LEVEL=DEBUG
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-continuation</artifactId>
|
<artifactId>jetty-continuation</artifactId>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>jetty-deploy</artifactId>
|
<artifactId>jetty-deploy</artifactId>
|
||||||
|
@ -14,24 +14,6 @@
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.felix</groupId>
|
|
||||||
<artifactId>maven-bundle-plugin</artifactId>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<goals>
|
|
||||||
<goal>manifest</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<instructions>
|
|
||||||
<Import-Package>org.eclipse.jetty.jmx.*;resolution:=optional,*</Import-Package>
|
|
||||||
<_nouses>true</_nouses>
|
|
||||||
</instructions>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>findbugs-maven-plugin</artifactId>
|
<artifactId>findbugs-maven-plugin</artifactId>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# Deploy Feature
|
Enables webapplication deployment from the webapps directory.
|
||||||
#
|
|
||||||
|
|
||||||
[depend]
|
[depend]
|
||||||
webapp
|
webapp
|
||||||
|
|
|
@ -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;
|
final DebugListener _debugListener;
|
||||||
|
|
||||||
public NamePredicate(String name)
|
public DebugListenerBinding()
|
||||||
{
|
{
|
||||||
this.name = name;
|
this(new DebugListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public DebugListenerBinding(DebugListener debugListener)
|
||||||
public boolean match(Node<?> input)
|
|
||||||
{
|
{
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -97,6 +97,8 @@ public class GlobalWebappConfigBinding implements AppLifeCycle.Binding
|
||||||
Resource resource = Resource.newResource(app.getOriginId());
|
Resource resource = Resource.newResource(app.getOriginId());
|
||||||
File file = resource.getFile();
|
File file = resource.getFile();
|
||||||
jettyXmlConfig.getIdMap().put("Server",app.getDeploymentManager().getServer());
|
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.webapp",file.getCanonicalPath());
|
||||||
jettyXmlConfig.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
|
jettyXmlConfig.getProperties().put("jetty.webapps",file.getParentFile().getCanonicalPath());
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ public class WebAppProvider extends ScanningAppProvider
|
||||||
|
|
||||||
if (resource.exists() && FileID.isXmlFile(file))
|
if (resource.exists() && FileID.isXmlFile(file))
|
||||||
{
|
{
|
||||||
XmlConfiguration xmlc = new XmlConfiguration(resource.getURL())
|
XmlConfiguration xmlc = new XmlConfiguration(resource.getURI().toURL())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void initializeDefaults(Object context)
|
public void initializeDefaults(Object context)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-project</artifactId>
|
<artifactId>jetty-project</artifactId>
|
||||||
<version>9.3.4-SNAPSHOT</version>
|
<version>9.4.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>jetty-distribution</artifactId>
|
<artifactId>jetty-distribution</artifactId>
|
||||||
<name>Jetty :: Distribution Assemblies</name>
|
<name>Jetty :: Distribution Assemblies</name>
|
||||||
|
@ -441,8 +441,8 @@
|
||||||
<goal>copy-dependencies</goal>
|
<goal>copy-dependencies</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.toolchain,org.mortbay.jasper,org.eclipse.jetty.orbit</includeGroupIds>
|
<includeGroupIds>org.eclipse.jetty,org.eclipse.jetty.toolchain,org.mortbay.jasper,org.eclipse.jetty.orbit,org.eclipse.jdt.core.compiler</includeGroupIds>
|
||||||
<includeArtifactIds>apache-jsp,apache-el,org.eclipse.jdt.core</includeArtifactIds>
|
<includeArtifactIds>apache-jsp,apache-el,ecj</includeArtifactIds>
|
||||||
<includeTypes>jar</includeTypes>
|
<includeTypes>jar</includeTypes>
|
||||||
<prependGroupId>true</prependGroupId>
|
<prependGroupId>true</prependGroupId>
|
||||||
<outputDirectory>${assembly-directory}/lib/apache-jsp</outputDirectory>
|
<outputDirectory>${assembly-directory}/lib/apache-jsp</outputDirectory>
|
||||||
|
@ -710,6 +710,11 @@
|
||||||
<artifactId>jetty-proxy</artifactId>
|
<artifactId>jetty-proxy</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-unixsocket</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.fcgi</groupId>
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
<artifactId>fcgi-server</artifactId>
|
<artifactId>fcgi-server</artifactId>
|
||||||
|
@ -778,6 +783,11 @@
|
||||||
<artifactId>jetty-infinispan</artifactId>
|
<artifactId>jetty-infinispan</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.gcloud</groupId>
|
||||||
|
<artifactId>gcloud-session-manager</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-nosql</artifactId>
|
<artifactId>jetty-nosql</artifactId>
|
||||||
|
|
|
@ -166,7 +166,7 @@ then
|
||||||
ETC=$HOME/etc
|
ETC=$HOME/etc
|
||||||
fi
|
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
|
if [ -f "$CONFIG" ] ; then
|
||||||
readConfig "$CONFIG"
|
readConfig "$CONFIG"
|
||||||
fi
|
fi
|
||||||
|
@ -445,7 +445,7 @@ case "$ACTION" in
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$JETTY_USER" ]
|
if [ -n "$JETTY_USER" ] && [ `whoami` != "$JETTY_USER" ]
|
||||||
then
|
then
|
||||||
unset SU_SHELL
|
unset SU_SHELL
|
||||||
if [ "$JETTY_SHELL" ]
|
if [ "$JETTY_SHELL" ]
|
||||||
|
@ -457,11 +457,11 @@ case "$ACTION" in
|
||||||
chown "$JETTY_USER" "$JETTY_PID"
|
chown "$JETTY_USER" "$JETTY_PID"
|
||||||
# FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
|
# FIXME: Broken solution: wordsplitting, pathname expansion, arbitrary command execution, etc.
|
||||||
su - "$JETTY_USER" $SU_SHELL -c "
|
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 \$!
|
disown \$!
|
||||||
echo \$! > '$JETTY_PID'"
|
echo \$! > '$JETTY_PID'"
|
||||||
else
|
else
|
||||||
"${RUN_CMD[@]}" &
|
"${RUN_CMD[@]}" > /dev/null &
|
||||||
disown $!
|
disown $!
|
||||||
echo $! > "$JETTY_PID"
|
echo $! > "$JETTY_PID"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# Hawtio x module
|
Deploys the Hawtio console as a webapplication.
|
||||||
#
|
|
||||||
|
|
||||||
[depend]
|
[depend]
|
||||||
stats
|
stats
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#
|
[description]
|
||||||
# JAMon Jetty module
|
Deploys the JAMon webapplication
|
||||||
#
|
|
||||||
|
|
||||||
[depend]
|
[depend]
|
||||||
stats
|
stats
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue