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:
Jan Bartel 2015-12-03 16:57:51 +11:00
commit 9ff55cb301
705 changed files with 22285 additions and 11225 deletions

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,6 +1,5 @@
#
# Apache JSP Module
#
[description]
Enables use of the apache implementation of JSP
[name]
apache-jsp

View File

@ -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>

View File

@ -1,6 +1,5 @@
#
# Apache JSTL
#
[description]
Enables the apache version of JSTL
[name]
apache-jstl

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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");

View File

@ -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",

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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]

View File

@ -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;
}

View File

@ -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);
}
}
}
}

View File

@ -201,7 +201,7 @@ public class Util
}
case Type.OBJECT:
{
return (Loader.loadClass(null, t.getClassName()));
return (Loader.loadClass(t.getClassName()));
}
case Type.SHORT:
{

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,6 +1,5 @@
#
# [EXPERIMENTAL] CDI / Weld Jetty module
#
[description]
Experimental CDI/Weld integration
[depend]
deploy

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,6 +1,5 @@
#
# Client Feature
#
[description]
Adds the Jetty HTTP client to the server classpath.
[lib]
lib/jetty-client-${jetty.version}.jar

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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";

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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()
{

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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
*/

View File

@ -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);
}
}

View File

@ -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()
{

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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>
* &lt;form method="POST" enctype="multipart/form-data" accept-charset="UTF-8"&gt;
* &lt;input type="text" name="field" value="foo" /&gt;
* &lt;input type="file" name="icon" /&gt;
* &lt;/form&gt;
* </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
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}

View File

@ -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());

View File

@ -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
{

View File

@ -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
{

View File

@ -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());

View File

@ -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();

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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));

View File

@ -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("" +

View File

@ -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";

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -1,6 +1,5 @@
#
# Deploy Feature
#
[description]
Enables webapplication deployment from the webapps directory.
[depend]
webapp

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -1,6 +1,5 @@
#
# Hawtio x module
#
[description]
Deploys the Hawtio console as a webapplication.
[depend]
stats

View File

@ -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