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