Merged branch 'jetty-10.0.x' into 'jetty-10.0.x-3952-server_direct_heap_bytebuffers'.
This commit is contained in:
commit
985d98296b
|
@ -12,10 +12,7 @@
|
|||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.2</version>
|
||||
<configuration>
|
||||
<!-- we would prefer not deploy it but we need as it's not discovered as part of reactor
|
||||
as it's a dependency of a plugin
|
||||
<skip>true</skip>
|
||||
-->
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
|
|
@ -33,23 +33,12 @@
|
|||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<!--
|
||||
Eclipse Jetty Specific.
|
||||
===========================================================================================
|
||||
-->
|
||||
|
||||
<module name="SuppressionCommentFilter">
|
||||
<property name="offCommentFormat" value="@checkstyle-disable-check : ([\w\|]+)"/>
|
||||
<property name="onCommentFormat" value="@checkstyle-enable-check : ([\w\|]+)"/>
|
||||
<property name="checkFormat" value="$1"/>
|
||||
</module>
|
||||
|
||||
<!-- Check abbreviations(consecutive capital letters) length in identifier name -->
|
||||
<module name="AbbreviationAsWordInName">
|
||||
<property name="ignoreFinal" value="true"/>
|
||||
<property name="allowedAbbreviations" value="ALPN, ASCII, AWT, CRLDP, CRLF, FCGI, GZIP, HTTP, HTTPS, ID, IP, ISO8859, JAAS, JDBC, JMXRMI, JNDI, JPMS, JSON, JSTL, LDAP, PROXY, RFC, SPNEGO, URI, URL, UTF8, XML"/>
|
||||
</module>
|
||||
|
||||
<!-- Location of Annotations -->
|
||||
<module name="AnnotationLocation">
|
||||
<property name="allowSamelineSingleParameterlessAnnotation" value="false"/>
|
||||
|
@ -90,7 +79,7 @@
|
|||
|
||||
<!-- Indentation Rules -->
|
||||
<module name="Indentation">
|
||||
<property name="throwsIndent" value="0"/>
|
||||
<property name="arrayInitIndent" value="8"/>
|
||||
</module>
|
||||
|
||||
<!-- Interface Type Parameter Name -->
|
||||
|
@ -215,7 +204,9 @@
|
|||
</module>
|
||||
|
||||
<!-- all switch statements should have "default" label declared -->
|
||||
<!-- Disabled: Is super noisy
|
||||
<module name="MissingSwitchDefault"/>
|
||||
-->
|
||||
|
||||
<!-- prevent line wrapping of import / package statements -->
|
||||
<module name="NoLineWrap"/>
|
||||
|
@ -226,9 +217,6 @@
|
|||
<!-- Filename and Classname match -->
|
||||
<module name="OuterTypeFilename"/>
|
||||
|
||||
<!-- Checks that overload methods are grouped together -->
|
||||
<module name="OverloadMethodsDeclarationOrder"/>
|
||||
|
||||
<!--
|
||||
Checks based on the Java Language Specification recommendations.
|
||||
https://docs.oracle.com/javase/specs/jls/se8/html/index.html
|
||||
|
@ -284,8 +272,5 @@
|
|||
<module name="UpperEll"/>
|
||||
<!-- TODO: look for float / double version of above -->
|
||||
|
||||
<!-- Checks the distance between declaration of variable and its first usage -->
|
||||
<module name="VariableDeclarationUsageDistance"/>
|
||||
|
||||
</module>
|
||||
</module>
|
||||
|
|
|
@ -188,11 +188,12 @@ public class AnnotationConfiguration extends AbstractConfiguration
|
|||
|
||||
/**
|
||||
* ServletContainerInitializerOrdering
|
||||
*
|
||||
* A list of classnames of ServletContainerInitializers in the order in which
|
||||
* they are to be called back. One name only in the list can be "*", which is a
|
||||
* <p>Applies an ordering to the {@link ServletContainerInitializer}s for the context, using
|
||||
* the value of the "org.eclipse.jetty.containerInitializerOrder" context attribute.
|
||||
* The attribute value is a list of classnames of ServletContainerInitializers in the order in which
|
||||
* they are to be called. One name only in the list can be "*", which is a
|
||||
* wildcard which matches any other ServletContainerInitializer name not already
|
||||
* matched.
|
||||
* matched.</p>
|
||||
*/
|
||||
public class ServletContainerInitializerOrdering
|
||||
{
|
||||
|
|
|
@ -5,14 +5,34 @@
|
|||
<version>10.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||
<artifactId>cdi-2</artifactId>
|
||||
<name>Jetty :: CDI 2</name>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-cdi</artifactId>
|
||||
<name>Jetty :: CDI</name>
|
||||
<url>http://www.eclipse.org/jetty</url>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.cdi2</bundle-symbolic-name>
|
||||
<bundle-symbolic-name>${project.groupId}.cdi</bundle-symbolic-name>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-annotations</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<Call name="setAttribute">
|
||||
<Arg type="String">org.eclipse.jetty.cdi</Arg>
|
||||
<Arg><Property name="jetty.cdi.mode" default="CdiSpiDecorator"/></Arg>
|
||||
</Call>
|
||||
</Configure>
|
|
@ -0,0 +1,18 @@
|
|||
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Configures Jetty to use the "CdiDecoratingListener" as the default
|
||||
CDI integration mode that allows a webapp to register it's own CDI
|
||||
decorator.
|
||||
|
||||
[tag]
|
||||
cdi
|
||||
|
||||
[provides]
|
||||
cdi-mode
|
||||
|
||||
[depend]
|
||||
cdi
|
||||
|
||||
[ini]
|
||||
jetty.cdi.mode=CdiDecoratingListener
|
|
@ -0,0 +1,17 @@
|
|||
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Configures Jetty to use the "CdiSpiDecorator" that calls the CDI SPI
|
||||
as the default CDI integration mode.
|
||||
|
||||
[tag]
|
||||
cdi
|
||||
|
||||
[provides]
|
||||
cdi-mode
|
||||
|
||||
[depend]
|
||||
cdi
|
||||
|
||||
[ini]
|
||||
jetty.cdi.mode=CdiSpiDecorator
|
|
@ -1,7 +1,34 @@
|
|||
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Jetty setup to support Weld/CDI2 with WELD inside the webapp
|
||||
Support for CDI inside the webapp.
|
||||
This module does not provide CDI, but configures jetty to support various
|
||||
integration modes with a CDI implementation on the webapp classpath.
|
||||
CDI integration modes can be selected per webapp with the "org.eclipse.jetty.cdi"
|
||||
init parameter or defaults to the mode set by the "org.eclipse.jetty.cdi" server
|
||||
attribute (which is initialised from the "jetty.cdi.mode" start property).
|
||||
Supported modes are:
|
||||
CdiSpiDecorator - Jetty will call the CDI SPI within the webapp to decorate
|
||||
objects (default).
|
||||
CdiDecoratingLister - The webapp may register a decorator on the context attribute
|
||||
"org.eclipse.jetty.cdi.decorator".
|
||||
|
||||
[tag]
|
||||
cdi
|
||||
|
||||
[provides]
|
||||
cdi
|
||||
|
||||
[depend]
|
||||
cdi2
|
||||
deploy
|
||||
|
||||
[xml]
|
||||
etc/cdi/jetty-cdi.xml
|
||||
|
||||
[lib]
|
||||
lib/jetty-cdi-${jetty.version}.jar
|
||||
lib/apache-jsp/org.mortbay.jasper.apache-el-*.jar
|
||||
|
||||
[ini]
|
||||
jetty.webapp.addSystemClasses+=,org.eclipse.jetty.cdi.CdiServletContainerInitializer
|
||||
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.cdi.CdiServletContainerInitializer
|
|
@ -1,20 +0,0 @@
|
|||
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Jetty setup to support Weld/CDI2 with WELD inside the webapp
|
||||
|
||||
[depend]
|
||||
annotations
|
||||
|
||||
[lib]
|
||||
lib/apache-jsp/org.mortbay.jasper.apache-el-*.jar
|
||||
|
||||
[ini]
|
||||
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.util.Decorator,-org.eclipse.jetty.util.DecoratedObjectFactory
|
||||
jetty.webapp.addServerClasses+=,-org.eclipse.jetty.server.handler.ContextHandler.,-org.eclipse.jetty.server.handler.ContextHandler,-org.eclipse.jetty.servlet.ServletContextHandler
|
||||
|
||||
|
||||
[license]
|
||||
Weld is an open source project hosted on Github and released under the Apache 2.0 license.
|
||||
http://weld.cdi-spec.org/
|
||||
http://www.apache.org/licenses/LICENSE-2.0.html
|
|
@ -16,23 +16,21 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.websocket.common.message;
|
||||
package org.eclipse.jetty.cdi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.eclipse.jetty.servlet.DecoratingListener;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
|
||||
public class NullMessage implements MessageAppender
|
||||
/**
|
||||
* A DecoratingListener that listens for "org.eclipse.jetty.cdi.decorator"
|
||||
*/
|
||||
class CdiDecoratingListener extends DecoratingListener
|
||||
{
|
||||
public static final MessageAppender INSTANCE = new NullMessage();
|
||||
public static final String MODE = "CdiDecoratingListener";
|
||||
public static final String ATTRIBUTE = "org.eclipse.jetty.cdi.decorator";
|
||||
|
||||
@Override
|
||||
public void appendFrame(ByteBuffer framePayload, boolean isLast)
|
||||
{
|
||||
// consume payload
|
||||
framePayload.position(framePayload.limit());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageComplete()
|
||||
public CdiDecoratingListener(ServletContextHandler contextHandler)
|
||||
{
|
||||
super(contextHandler, ATTRIBUTE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.cdi;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import javax.servlet.ServletContainerInitializer;
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import org.eclipse.jetty.annotations.AnnotationConfiguration;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* <p>A {@link ServletContainerInitializer} that introspects for a CDI API
|
||||
* implementation within a web application and applies an integration
|
||||
* mode if CDI is found. CDI integration modes can be selected per webapp with
|
||||
* the "org.eclipse.jetty.cdi" init parameter or default to the mode set by the
|
||||
* "org.eclipse.jetty.cdi" server attribute. Supported modes are:</p>
|
||||
* <dl>
|
||||
* <dt>CdiSpiDecorator</dt>
|
||||
* <dd>Jetty will call the CDI SPI within the webapp to decorate objects (default).</dd>
|
||||
* <dt>CdiDecoratingLister</dt>
|
||||
* <dd>The webapp may register a decorator on the context attribute
|
||||
* "org.eclipse.jetty.cdi.decorator".</dd>
|
||||
* </dl>
|
||||
*
|
||||
* @see AnnotationConfiguration.ServletContainerInitializerOrdering
|
||||
*/
|
||||
public class CdiServletContainerInitializer implements ServletContainerInitializer
|
||||
{
|
||||
public static final String CDI_INTEGRATION_ATTRIBUTE = "org.eclipse.jetty.cdi";
|
||||
private static final Logger LOG = Log.getLogger(CdiServletContainerInitializer.class);
|
||||
|
||||
@Override
|
||||
public void onStartup(Set<Class<?>> c, ServletContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ServletContextHandler context = ServletContextHandler.getServletContextHandler(ctx);
|
||||
Objects.requireNonNull(context);
|
||||
|
||||
// Test if CDI is in the webapp by trying to load the CDI class.
|
||||
ClassLoader loader = context.getClassLoader();
|
||||
if (loader == null)
|
||||
Loader.loadClass("javax.enterprise.inject.spi.CDI");
|
||||
else
|
||||
loader.loadClass("javax.enterprise.inject.spi.CDI");
|
||||
|
||||
String mode = ctx.getInitParameter(CDI_INTEGRATION_ATTRIBUTE);
|
||||
if (mode == null)
|
||||
{
|
||||
mode = (String)context.getServer().getAttribute(CDI_INTEGRATION_ATTRIBUTE);
|
||||
if (mode == null)
|
||||
mode = CdiSpiDecorator.MODE;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case CdiSpiDecorator.MODE:
|
||||
context.getObjectFactory().addDecorator(new CdiSpiDecorator(context));
|
||||
break;
|
||||
|
||||
case CdiDecoratingListener.MODE:
|
||||
context.addEventListener(new CdiDecoratingListener(context));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(mode);
|
||||
}
|
||||
|
||||
context.setAttribute(CDI_INTEGRATION_ATTRIBUTE, mode);
|
||||
LOG.info(mode + " enabled in " + ctx);
|
||||
}
|
||||
catch (UnsupportedOperationException | ClassNotFoundException e)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("CDI not found in " + ctx, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.cdi;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.util.Decorator;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* A Decorator that invokes the CDI provider within a webapp to decorate objects created by
|
||||
* the contexts {@link org.eclipse.jetty.util.DecoratedObjectFactory}
|
||||
* (typically Listeners, Filters and Servlets).
|
||||
* The CDI provider is invoked using {@link MethodHandle}s to avoid any CDI instance
|
||||
* or dependencies within the server scope. The code invoked is equivalent to:
|
||||
* <pre>
|
||||
* public <T> T decorate(T o)
|
||||
* {
|
||||
* BeanManager manager = CDI.current().getBeanManager();
|
||||
* manager.createInjectionTarget(manager.createAnnotatedType((Class<T>)o.getClass()))
|
||||
* .inject(o,manager.createCreationalContext(null));
|
||||
* return o;
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class CdiSpiDecorator implements Decorator
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(CdiServletContainerInitializer.class);
|
||||
public static final String MODE = "CdiSpiDecorator";
|
||||
|
||||
private final ServletContextHandler _context;
|
||||
private final Map<Object, Decorated> _decorated = new HashMap<>();
|
||||
|
||||
private final MethodHandle _current;
|
||||
private final MethodHandle _getBeanManager;
|
||||
private final MethodHandle _createAnnotatedType;
|
||||
private final MethodHandle _createInjectionTarget;
|
||||
private final MethodHandle _createCreationalContext;
|
||||
private final MethodHandle _inject;
|
||||
private final MethodHandle _dispose;
|
||||
private final MethodHandle _release;
|
||||
|
||||
public CdiSpiDecorator(ServletContextHandler context) throws UnsupportedOperationException
|
||||
{
|
||||
_context = context;
|
||||
ClassLoader classLoader = _context.getClassLoader();
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> cdiClass = classLoader.loadClass("javax.enterprise.inject.spi.CDI");
|
||||
Class<?> beanManagerClass = classLoader.loadClass("javax.enterprise.inject.spi.BeanManager");
|
||||
Class<?> annotatedTypeClass = classLoader.loadClass("javax.enterprise.inject.spi.AnnotatedType");
|
||||
Class<?> injectionTargetClass = classLoader.loadClass("javax.enterprise.inject.spi.InjectionTarget");
|
||||
Class<?> creationalContextClass = classLoader.loadClass("javax.enterprise.context.spi.CreationalContext");
|
||||
Class<?> contextualClass = classLoader.loadClass("javax.enterprise.context.spi.Contextual");
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
_current = lookup.findStatic(cdiClass, "current", MethodType.methodType(cdiClass));
|
||||
_getBeanManager = lookup.findVirtual(cdiClass, "getBeanManager", MethodType.methodType(beanManagerClass));
|
||||
_createAnnotatedType = lookup.findVirtual(beanManagerClass, "createAnnotatedType", MethodType.methodType(annotatedTypeClass, Class.class));
|
||||
_createInjectionTarget = lookup.findVirtual(beanManagerClass, "createInjectionTarget", MethodType.methodType(injectionTargetClass, annotatedTypeClass));
|
||||
_createCreationalContext = lookup.findVirtual(beanManagerClass, "createCreationalContext", MethodType.methodType(creationalContextClass, contextualClass));
|
||||
_inject = lookup.findVirtual(injectionTargetClass, "inject", MethodType.methodType(Void.TYPE, Object.class, creationalContextClass));
|
||||
_dispose = lookup.findVirtual(injectionTargetClass, "dispose", MethodType.methodType(Void.TYPE, Object.class));
|
||||
_release = lookup.findVirtual(creationalContextClass, "release", MethodType.methodType(Void.TYPE));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new UnsupportedOperationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate an object.
|
||||
* <p>The signature of this method must match what is introspected for by the
|
||||
* Jetty DecoratingListener class. It is invoked dynamically.</p>
|
||||
*
|
||||
* @param o The object to be decorated
|
||||
* @param <T> The type of the object to be decorated
|
||||
* @return The decorated object
|
||||
*/
|
||||
public <T> T decorate(T o)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("decorate {} in {}", o, _context);
|
||||
|
||||
_decorated.put(o, new Decorated(o));
|
||||
}
|
||||
catch (Throwable th)
|
||||
{
|
||||
LOG.warn("Unable to decorate " + o, th);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a decorated object.
|
||||
* <p>The signature of this method must match what is introspected for by the
|
||||
* Jetty DecoratingListener class. It is invoked dynamically.</p>
|
||||
*
|
||||
* @param o The object to be destroyed
|
||||
*/
|
||||
public void destroy(Object o)
|
||||
{
|
||||
try
|
||||
{
|
||||
Decorated decorated = _decorated.remove(o);
|
||||
if (decorated != null)
|
||||
decorated.destroy(o);
|
||||
}
|
||||
catch (Throwable th)
|
||||
{
|
||||
LOG.warn("Unable to destroy " + o, th);
|
||||
}
|
||||
}
|
||||
|
||||
private class Decorated
|
||||
{
|
||||
private final Object _injectionTarget;
|
||||
private final Object _creationalContext;
|
||||
|
||||
Decorated(Object o) throws Throwable
|
||||
{
|
||||
// BeanManager manager = CDI.current().getBeanManager();
|
||||
Object manager = _getBeanManager.invoke(_current.invoke());
|
||||
// AnnotatedType annotatedType = manager.createAnnotatedType((Class<T>)o.getClass());
|
||||
Object annotatedType = _createAnnotatedType.invoke(manager, o.getClass());
|
||||
// CreationalContext creationalContext = manager.createCreationalContext(null);
|
||||
_creationalContext = _createCreationalContext.invoke(manager, null);
|
||||
// InjectionTarget injectionTarget = manager.createInjectionTarget();
|
||||
_injectionTarget = _createInjectionTarget.invoke(manager, annotatedType);
|
||||
// injectionTarget.inject(o, creationalContext);
|
||||
_inject.invoke(_injectionTarget, o, _creationalContext);
|
||||
}
|
||||
|
||||
public void destroy(Object o) throws Throwable
|
||||
{
|
||||
_dispose.invoke(_injectionTarget, o);
|
||||
_release.invoke(_creationalContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.eclipse.jetty.cdi.CdiServletContainerInitializer
|
|
@ -72,7 +72,7 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
|
|||
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory());
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
connector.connect(address, context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -509,13 +509,26 @@ public class HttpClient extends ContainerLifeCycle
|
|||
* @param port the destination port
|
||||
* @return the destination
|
||||
* @see #getDestinations()
|
||||
* @deprecated use {@link #resolveDestination(Request)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public Destination getDestination(String scheme, String host, int port)
|
||||
{
|
||||
Origin origin = createOrigin(scheme, host, port);
|
||||
return resolveDestination(new HttpDestination.Key(origin, null));
|
||||
}
|
||||
|
||||
public Destination resolveDestination(Request request)
|
||||
{
|
||||
Origin origin = createOrigin(request.getScheme(), request.getHost(), request.getPort());
|
||||
HttpClientTransport transport = getTransport();
|
||||
HttpDestination.Key destinationKey = transport.newDestinationKey((HttpRequest)request, origin);
|
||||
HttpDestination destination = resolveDestination(destinationKey);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Resolved {} for {}", destination, request);
|
||||
return destination;
|
||||
}
|
||||
|
||||
private Origin createOrigin(String scheme, String host, int port)
|
||||
{
|
||||
if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme) &&
|
||||
|
@ -529,7 +542,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
return new Origin(scheme, host, port);
|
||||
}
|
||||
|
||||
private HttpDestination resolveDestination(HttpDestination.Key key)
|
||||
HttpDestination resolveDestination(HttpDestination.Key key)
|
||||
{
|
||||
HttpDestination destination = destinations.get(key);
|
||||
if (destination == null)
|
||||
|
@ -568,16 +581,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
|
||||
protected void send(final HttpRequest request, List<Response.ResponseListener> listeners)
|
||||
{
|
||||
Origin origin = createOrigin(request.getScheme(), request.getHost(), request.getPort());
|
||||
HttpClientTransport transport = getTransport();
|
||||
HttpDestination.Key destinationKey = null;
|
||||
if (transport instanceof HttpClientTransport.Dynamic)
|
||||
destinationKey = ((HttpClientTransport.Dynamic)transport).newDestinationKey(request, origin);
|
||||
if (destinationKey == null)
|
||||
destinationKey = new HttpDestination.Key(origin, null);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Selected {} for {}", destinationKey, request);
|
||||
HttpDestination destination = resolveDestination(destinationKey);
|
||||
HttpDestination destination = (HttpDestination)resolveDestination(request);
|
||||
destination.send(request, listeners);
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,15 @@ public interface HttpClientTransport extends ClientConnectionFactory
|
|||
*/
|
||||
public void setHttpClient(HttpClient client);
|
||||
|
||||
/**
|
||||
* Creates a new Key with the given request and origin.
|
||||
*
|
||||
* @param request the request that triggers the creation of the Key
|
||||
* @param origin the origin of the server for the request
|
||||
* @return a Key that identifies a destination
|
||||
*/
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin);
|
||||
|
||||
/**
|
||||
* Creates a new, transport-specific, {@link HttpDestination} object.
|
||||
* <p>
|
||||
|
@ -78,20 +87,4 @@ public interface HttpClientTransport extends ClientConnectionFactory
|
|||
* @param factory the factory for ConnectionPool instances
|
||||
*/
|
||||
public void setConnectionPoolFactory(ConnectionPool.Factory factory);
|
||||
|
||||
/**
|
||||
* Specifies whether a {@link HttpClientTransport} is dynamic.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Dynamic
|
||||
{
|
||||
/**
|
||||
* Creates a new Key with the given request and origin.
|
||||
*
|
||||
* @param request the request that triggers the creation of the Key
|
||||
* @param origin the origin of the server for the request
|
||||
* @return a Key that identifies a destination
|
||||
*/
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,15 @@ package org.eclipse.jetty.client;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Objects;
|
||||
|
||||
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.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
|
@ -37,7 +38,6 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
||||
public class HttpProxy extends ProxyConfiguration.Proxy
|
||||
{
|
||||
|
@ -50,7 +50,12 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
|
||||
public HttpProxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure);
|
||||
this(address, secure, new HttpDestination.Protocol(List.of("http/1.1"), false));
|
||||
}
|
||||
|
||||
public HttpProxy(Origin.Address address, boolean secure, HttpDestination.Protocol protocol)
|
||||
{
|
||||
super(address, secure, Objects.requireNonNull(protocol));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -61,9 +66,14 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
|
||||
@Override
|
||||
public URI getURI()
|
||||
{
|
||||
return URI.create(newOrigin().asString());
|
||||
}
|
||||
|
||||
private Origin newOrigin()
|
||||
{
|
||||
String scheme = isSecure() ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString();
|
||||
return URI.create(new Origin(scheme, getAddress()).asString());
|
||||
return new Origin(scheme, getAddress());
|
||||
}
|
||||
|
||||
private class HttpProxyClientConnectionFactory implements ClientConnectionFactory
|
||||
|
@ -79,33 +89,26 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
SslContextFactory sslContextFactory = destination.getHttpClient().getSslContextFactory();
|
||||
if (destination.isSecure())
|
||||
HttpDestination.Protocol serverProtocol = destination.getKey().getProtocol();
|
||||
boolean sameProtocol = proxySpeaksServerProtocol(serverProtocol);
|
||||
if (destination.isSecure() || !sameProtocol)
|
||||
{
|
||||
if (sslContextFactory != null)
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
Promise<Connection> wrapped = promise;
|
||||
if (promise instanceof Promise.Wrapper)
|
||||
wrapped = ((Promise.Wrapper<Connection>)promise).unwrap();
|
||||
if (wrapped instanceof TunnelPromise)
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
Promise<Connection> wrapped = promise;
|
||||
if (promise instanceof Promise.Wrapper)
|
||||
wrapped = ((Promise.Wrapper<Connection>)promise).unwrap();
|
||||
if (wrapped instanceof TunnelPromise)
|
||||
{
|
||||
((TunnelPromise)wrapped).setEndPoint(endPoint);
|
||||
return connectionFactory.newConnection(endPoint, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace the promise with the proxy promise that creates the tunnel to the server.
|
||||
CreateTunnelPromise tunnelPromise = new CreateTunnelPromise(connectionFactory, endPoint, promise, context);
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, tunnelPromise);
|
||||
return connectionFactory.newConnection(endPoint, context);
|
||||
}
|
||||
// In case the server closes the tunnel (e.g. proxy authentication
|
||||
// required: 407 + Connection: close), we will open another tunnel
|
||||
// so we need to tell the promise about the new EndPoint.
|
||||
((TunnelPromise)wrapped).setEndPoint(endPoint);
|
||||
return connectionFactory.newConnection(endPoint, context);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Cannot tunnel request, missing " +
|
||||
SslContextFactory.class.getName() + " in " + HttpClient.class.getName());
|
||||
return newProxyConnection(endPoint, context);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -113,6 +116,34 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
return connectionFactory.newConnection(endPoint, context);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean proxySpeaksServerProtocol(HttpDestination.Protocol serverProtocol)
|
||||
{
|
||||
return serverProtocol != null && getProtocol().getProtocols().stream().anyMatch(p -> serverProtocol.getProtocols().stream().anyMatch(p::equalsIgnoreCase));
|
||||
}
|
||||
|
||||
private org.eclipse.jetty.io.Connection newProxyConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
// Replace the promise with the proxy promise that creates the tunnel to the server.
|
||||
@SuppressWarnings("unchecked")
|
||||
Promise<Connection> promise = (Promise<Connection>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
CreateTunnelPromise tunnelPromise = new CreateTunnelPromise(connectionFactory, endPoint, promise, context);
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, tunnelPromise);
|
||||
|
||||
// Replace the destination with the proxy destination.
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
HttpDestination proxyDestination = client.resolveDestination(new HttpDestination.Key(newOrigin(), getProtocol()));
|
||||
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, proxyDestination);
|
||||
try
|
||||
{
|
||||
return connectionFactory.newConnection(endPoint, context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,6 +170,8 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
@Override
|
||||
public void succeeded(Connection connection)
|
||||
{
|
||||
// Replace the promise back with the original.
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
tunnel(destination, connection);
|
||||
}
|
||||
|
@ -154,61 +187,28 @@ 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())
|
||||
.method(HttpMethod.CONNECT)
|
||||
.path(target)
|
||||
.header(HttpHeader.HOST, target)
|
||||
.idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS)
|
||||
.timeout(connectTimeout, TimeUnit.MILLISECONDS);
|
||||
Request connect = new TunnelRequest(httpClient, proxyAddress)
|
||||
.method(HttpMethod.CONNECT)
|
||||
.path(target)
|
||||
.header(HttpHeader.HOST, target);
|
||||
ProxyConfiguration.Proxy proxy = destination.getProxy();
|
||||
if (proxy != null && proxy.isSecure())
|
||||
if (proxy.isSecure())
|
||||
connect.scheme(HttpScheme.HTTPS.asString());
|
||||
|
||||
final HttpConversation conversation = ((HttpRequest)connect).getConversation();
|
||||
conversation.setAttribute(EndPoint.class.getName(), endPoint);
|
||||
|
||||
connect.attribute(Connection.class.getName(), new ProxyConnection(destination, connection, promise));
|
||||
|
||||
connection.send(connect, result ->
|
||||
{
|
||||
// The EndPoint may have changed during the conversation, get the latest.
|
||||
EndPoint endPoint = (EndPoint)conversation.getAttribute(EndPoint.class.getName());
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
Response response = result.getResponse();
|
||||
if (response.getStatus() == HttpStatus.OK_200)
|
||||
{
|
||||
tunnelSucceeded(endPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseException failure = new HttpResponseException("Unexpected " + response +
|
||||
" for " + result.getRequest(), response);
|
||||
tunnelFailed(endPoint, failure);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tunnelFailed(endPoint, result.getFailure());
|
||||
}
|
||||
});
|
||||
connection.send(connect, new TunnelListener(connect));
|
||||
}
|
||||
|
||||
private void tunnelSucceeded(EndPoint endPoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Replace the promise back with the original
|
||||
context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
ClientConnectionFactory sslConnectionFactory = client.newSslClientConnectionFactory(connectionFactory);
|
||||
HttpConnectionOverHTTP oldConnection = (HttpConnectionOverHTTP)endPoint.getConnection();
|
||||
org.eclipse.jetty.io.Connection newConnection = sslConnectionFactory.newConnection(endPoint, context);
|
||||
// Creating the connection will link the new Connection the EndPoint,
|
||||
// but we need the old Connection linked for the upgrade to do its job.
|
||||
endPoint.setConnection(oldConnection);
|
||||
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||
if (destination.isSecure())
|
||||
connectionFactory = destination.newSslClientConnectionFactory(connectionFactory);
|
||||
var oldConnection = endPoint.getConnection();
|
||||
var newConnection = connectionFactory.newConnection(endPoint, context);
|
||||
endPoint.upgrade(newConnection);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
|
||||
|
@ -224,6 +224,40 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
endPoint.close();
|
||||
promise.failed(failure);
|
||||
}
|
||||
|
||||
private class TunnelListener extends Response.Listener.Adapter
|
||||
{
|
||||
private final HttpConversation conversation;
|
||||
|
||||
private TunnelListener(Request request)
|
||||
{
|
||||
this.conversation = ((HttpRequest)request).getConversation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Response response)
|
||||
{
|
||||
// The EndPoint may have changed during the conversation, get the latest.
|
||||
EndPoint endPoint = (EndPoint)conversation.getAttribute(EndPoint.class.getName());
|
||||
if (response.getStatus() == HttpStatus.OK_200)
|
||||
{
|
||||
tunnelSucceeded(endPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpResponseException failure = new HttpResponseException("Unexpected " + response +
|
||||
" for " + response.getRequest(), response);
|
||||
tunnelFailed(endPoint, failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isFailed())
|
||||
tunnelFailed(endPoint, result.getFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyConnection implements Connection
|
||||
|
@ -296,4 +330,12 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
conversation.setAttribute(EndPoint.class.getName(), endPoint);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TunnelRequest extends HttpRequest
|
||||
{
|
||||
private TunnelRequest(HttpClient client, Origin.Address address)
|
||||
{
|
||||
super(client, new HttpConversation(), URI.create("http://" + address.asString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -609,7 +609,7 @@ public abstract class HttpReceiver
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Action process() throws Throwable
|
||||
protected Action process()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
|
|
@ -76,6 +76,7 @@ public class HttpRequest implements Request
|
|||
private String query;
|
||||
private String method = HttpMethod.GET.asString();
|
||||
private HttpVersion version = HttpVersion.HTTP_1_1;
|
||||
private boolean versionExplicit;
|
||||
private long idleTimeout = -1;
|
||||
private long timeout;
|
||||
private long timeoutAt;
|
||||
|
@ -215,10 +216,16 @@ public class HttpRequest implements Request
|
|||
return version;
|
||||
}
|
||||
|
||||
public boolean isVersionExplicit()
|
||||
{
|
||||
return versionExplicit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request version(HttpVersion version)
|
||||
{
|
||||
this.version = Objects.requireNonNull(version);
|
||||
this.versionExplicit = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ public class ProxyAuthenticationProtocolHandler extends AuthenticationProtocolHa
|
|||
@Override
|
||||
protected URI getAuthenticationURI(Request request)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)getHttpClient().getDestination(request.getScheme(), request.getHost(), request.getPort());
|
||||
HttpDestination destination = (HttpDestination)getHttpClient().resolveDestination(request);
|
||||
ProxyConfiguration.Proxy proxy = destination.getProxy();
|
||||
return proxy != null ? proxy.getURI() : request.getURI();
|
||||
}
|
||||
|
|
|
@ -64,11 +64,13 @@ public class ProxyConfiguration
|
|||
private final Set<String> excluded = new HashSet<>();
|
||||
private final Origin.Address address;
|
||||
private final boolean secure;
|
||||
private final HttpDestination.Protocol protocol;
|
||||
|
||||
protected Proxy(Origin.Address address, boolean secure)
|
||||
protected Proxy(Origin.Address address, boolean secure, HttpDestination.Protocol protocol)
|
||||
{
|
||||
this.address = address;
|
||||
this.secure = secure;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +89,11 @@ public class ProxyConfiguration
|
|||
return secure;
|
||||
}
|
||||
|
||||
public HttpDestination.Protocol getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the list of origins that must be proxied
|
||||
* @see #matches(Origin)
|
||||
|
|
|
@ -45,7 +45,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
|
|||
|
||||
public Socks4Proxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure);
|
||||
super(address, secure, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -64,7 +64,7 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
|
|||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
|
@ -195,10 +195,9 @@ public class Socks4Proxy extends ProxyConfiguration.Proxy
|
|||
try
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpClient client = destination.getHttpClient();
|
||||
ClientConnectionFactory connectionFactory = this.connectionFactory;
|
||||
if (destination.isSecure())
|
||||
connectionFactory = client.newSslClientConnectionFactory(connectionFactory);
|
||||
connectionFactory = destination.newSslClientConnectionFactory(connectionFactory);
|
||||
org.eclipse.jetty.io.Connection newConnection = connectionFactory.newConnection(getEndPoint(), context);
|
||||
getEndPoint().upgrade(newConnection);
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.eclipse.jetty.util.Promise;
|
|||
* {@link Destination} holds a pool of {@link Connection}s, but allows to create unpooled
|
||||
* connections if the application wants full control over connection management via {@link #newConnection(Promise)}.
|
||||
* <p>
|
||||
* {@link Destination}s may be obtained via {@link HttpClient#getDestination(String, String, int)}
|
||||
* {@link Destination}s may be obtained via {@link HttpClient#resolveDestination(Request)}
|
||||
*/
|
||||
public interface Destination
|
||||
{
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.client.dynamic;
|
|||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -78,7 +79,7 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
* version, or request headers, or request attributes, or even request path) by overriding
|
||||
* {@link #newDestinationKey(HttpRequest, Origin)}.</p>
|
||||
*/
|
||||
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport implements HttpClientTransport.Dynamic
|
||||
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport
|
||||
{
|
||||
private final List<ClientConnectionFactory.Info> factoryInfos;
|
||||
private final List<String> protocols;
|
||||
|
@ -102,42 +103,48 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
super(connector);
|
||||
addBean(connector);
|
||||
if (factoryInfos.length == 0)
|
||||
throw new IllegalArgumentException("Missing ClientConnectionFactory");
|
||||
factoryInfos = new Info[]{HttpClientConnectionFactory.HTTP11};
|
||||
this.factoryInfos = Arrays.asList(factoryInfos);
|
||||
this.protocols = Arrays.stream(factoryInfos)
|
||||
.flatMap(info -> info.getProtocols().stream())
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
.flatMap(info -> info.getProtocols().stream())
|
||||
.distinct()
|
||||
.map(p -> p.toLowerCase(Locale.ENGLISH))
|
||||
.collect(Collectors.toList());
|
||||
for (ClientConnectionFactory.Info factoryInfo : factoryInfos)
|
||||
{
|
||||
addBean(factoryInfo);
|
||||
}
|
||||
setConnectionPoolFactory(destination ->
|
||||
new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1));
|
||||
new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), destination, 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
{
|
||||
boolean ssl = HttpScheme.HTTPS.is(request.getScheme());
|
||||
String http1 = "http/1.1";
|
||||
String http2 = ssl ? "h2" : "h2c";
|
||||
List<String> protocols = List.of();
|
||||
if (request.getVersion() == HttpVersion.HTTP_2)
|
||||
if (request.isVersionExplicit())
|
||||
{
|
||||
// The application is explicitly asking for HTTP/2, so exclude HTTP/1.1.
|
||||
if (this.protocols.contains(http2))
|
||||
protocols = List.of(http2);
|
||||
HttpVersion version = request.getVersion();
|
||||
String desired = version == HttpVersion.HTTP_2 ? http2 : http1;
|
||||
if (this.protocols.contains(desired))
|
||||
protocols = List.of(desired);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Preserve the order of protocols chosen by the application.
|
||||
// We need to keep multiple protocols in case the protocol
|
||||
// is negotiated: e.g. [http/1.1, h2] negotiates [h2], but
|
||||
// here we don't know yet what will be negotiated.
|
||||
protocols = this.protocols.stream()
|
||||
.filter(p -> p.equals("http/1.1") || p.equals(http2))
|
||||
.filter(p -> p.equals(http1) || p.equals(http2))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
if (protocols.isEmpty())
|
||||
return new HttpDestination.Key(origin, null);
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(protocols, ssl));
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(protocols, ssl && protocols.contains(http2)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,7 +173,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
else
|
||||
{
|
||||
factoryInfo = findClientConnectionFactoryInfo(protocol.getProtocols())
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol));
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for " + protocol));
|
||||
}
|
||||
}
|
||||
return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
|
@ -184,7 +191,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
throw new IOException("Could not negotiate protocol among " + alpnConnection.getProtocols());
|
||||
List<String> protocols = List.of(protocol);
|
||||
Info factoryInfo = findClientConnectionFactoryInfo(protocols)
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for negotiated protocol " + protocol));
|
||||
.orElseThrow(() -> new IOException("Cannot find " + ClientConnectionFactory.class.getSimpleName() + " for negotiated protocol " + protocol));
|
||||
return factoryInfo.getClientConnectionFactory().newConnection(endPoint, context);
|
||||
}
|
||||
catch (Throwable failure)
|
||||
|
@ -197,7 +204,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
private Optional<Info> findClientConnectionFactoryInfo(List<String> protocols)
|
||||
{
|
||||
return factoryInfos.stream()
|
||||
.filter(info -> info.matches(protocols))
|
||||
.findFirst();
|
||||
.filter(info -> info.matches(protocols))
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,15 @@
|
|||
package org.eclipse.jetty.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
|
||||
import org.eclipse.jetty.client.DuplexConnectionPool;
|
||||
import org.eclipse.jetty.client.DuplexHttpDestination;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
@ -35,6 +38,8 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
|
|||
@ManagedObject("The HTTP/1.1 client transport")
|
||||
public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTransport
|
||||
{
|
||||
public static final HttpDestination.Protocol HTTP11 = new HttpDestination.Protocol(List.of("http/1.1"), false);
|
||||
|
||||
public HttpClientTransportOverHTTP()
|
||||
{
|
||||
this(Math.max(1, ProcessorUtils.availableProcessors() / 2));
|
||||
|
@ -52,6 +57,12 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
setConnectionPoolFactory(destination -> new DuplexConnectionPool(destination, getHttpClient().getMaxConnectionsPerDestination(), destination));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
{
|
||||
return new HttpDestination.Key(origin, HTTP11);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.client.http;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -28,6 +29,7 @@ import java.util.concurrent.atomic.LongAdder;
|
|||
import org.eclipse.jetty.client.HttpConnection;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.IConnection;
|
||||
import org.eclipse.jetty.client.SendFailure;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
|
@ -256,6 +258,18 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements IConne
|
|||
return send(channel, exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void normalizeRequest(Request request)
|
||||
{
|
||||
super.normalizeRequest(request);
|
||||
if (request instanceof HttpProxy.TunnelRequest)
|
||||
{
|
||||
long connectTimeout = getHttpClient().getConnectTimeout();
|
||||
request.timeout(connectTimeout, TimeUnit.MILLISECONDS)
|
||||
.idleTimeout(2 * connectTimeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
|
|
|
@ -260,6 +260,12 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
if (exchange == null)
|
||||
return false;
|
||||
|
||||
if (HttpMethod.CONNECT.is(exchange.getRequest().getMethod()))
|
||||
{
|
||||
// Store the EndPoint even in case of non-200 responses.
|
||||
exchange.getRequest().getConversation().setAttribute(EndPoint.class.getName(), getHttpConnection().getEndPoint());
|
||||
}
|
||||
|
||||
return !responseHeaders(exchange);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import java.io.InterruptedIOException;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -55,7 +54,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
|
@ -85,22 +84,23 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
ContentResponse response = client.newRequest(host, port)
|
||||
var request = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.content(new StringContentProvider("0"))
|
||||
.onRequestSuccess(request ->
|
||||
.onRequestSuccess(r ->
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(r);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
|
||||
assertFalse(connection.getEndPoint().isOutputShutdown());
|
||||
})
|
||||
.send();
|
||||
});
|
||||
ContentResponse response = request.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertArrayEquals(data, response.getContent());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
request.startAsync();
|
||||
|
@ -122,27 +122,28 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
CountDownLatch resultLatch = new CountDownLatch(1);
|
||||
long idleTimeout = 1000;
|
||||
client.newRequest(host, port)
|
||||
var request = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
|
||||
.onRequestSuccess(request ->
|
||||
.onRequestSuccess(r ->
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(r);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
|
||||
assertFalse(connection.getEndPoint().isOutputShutdown());
|
||||
})
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
resultLatch.countDown();
|
||||
});
|
||||
request.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
resultLatch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(resultLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
}
|
||||
|
||||
|
@ -154,7 +155,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
|
@ -183,30 +184,31 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.allocate(8));
|
||||
CountDownLatch resultLatch = new CountDownLatch(1);
|
||||
client.newRequest(host, port)
|
||||
var request = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.content(content)
|
||||
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
|
||||
.onRequestSuccess(request ->
|
||||
.onRequestSuccess(r ->
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(r);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
|
||||
assertFalse(connection.getEndPoint().isOutputShutdown());
|
||||
})
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
resultLatch.countDown();
|
||||
});
|
||||
request.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
resultLatch.countDown();
|
||||
});
|
||||
content.offer(ByteBuffer.allocate(8));
|
||||
content.close();
|
||||
|
||||
assertTrue(resultLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
}
|
||||
|
||||
|
@ -217,7 +219,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setContentLength(0);
|
||||
|
@ -238,21 +240,22 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
ContentResponse response = client.newRequest(host, port)
|
||||
var request = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.onRequestSuccess(request ->
|
||||
.onRequestSuccess(r ->
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(r);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
HttpConnectionOverHTTP connection = (HttpConnectionOverHTTP)connectionPool.getActiveConnections().iterator().next();
|
||||
assertFalse(connection.getEndPoint().isOutputShutdown());
|
||||
})
|
||||
.onResponseHeaders(r -> r.getHeaders().remove(HttpHeader.CONNECTION))
|
||||
.send();
|
||||
.onResponseHeaders(r -> r.getHeaders().remove(HttpHeader.CONNECTION));
|
||||
ContentResponse response = request.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ public class HttpClientCustomProxyTest
|
|||
{
|
||||
private CAFEBABEProxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
super(address, secure);
|
||||
super(address, secure, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -45,12 +45,12 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
Destination destination = client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
Destination destination = client.resolveDestination(request);
|
||||
FuturePromise<Connection> futureConnection = new FuturePromise<>();
|
||||
destination.newConnection(futureConnection);
|
||||
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
|
||||
{
|
||||
Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scenario.getScheme());
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||
|
@ -71,11 +71,11 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
Destination destination = client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
Destination destination = client.resolveDestination(request);
|
||||
FuturePromise<Connection> futureConnection = new FuturePromise<>();
|
||||
destination.newConnection(futureConnection);
|
||||
Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
|
||||
Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scenario.getScheme());
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||
|
@ -105,14 +105,14 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
Destination destination = client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onResponseSuccess(response -> responseLatch.countDown());
|
||||
Destination destination = client.resolveDestination(request);
|
||||
FuturePromise<Connection> futureConnection = new FuturePromise<>();
|
||||
destination.newConnection(futureConnection);
|
||||
Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
Request request = client.newRequest(destination.getHost(), destination.getPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onResponseSuccess(response -> responseLatch.countDown());
|
||||
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
|
|
|
@ -120,10 +120,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
String path = "/";
|
||||
Response response = client.GET(scenario.getScheme() + "://" + host + ":" + port + path);
|
||||
Request request = client.newRequest(scenario.getScheme() + "://" + host + ":" + port + path);
|
||||
Response response = request.send();
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
long start = System.nanoTime();
|
||||
|
@ -182,7 +183,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_GET_ResponseWithContent(Scenario scenario) throws Exception
|
||||
{
|
||||
final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -206,8 +207,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_GET_WithParameters_ResponseWithContent(Scenario scenario) throws Exception
|
||||
{
|
||||
final String paramName1 = "a";
|
||||
final String paramName2 = "b";
|
||||
String paramName1 = "a";
|
||||
String paramName2 = "b";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -239,8 +240,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_GET_WithParametersMultiValued_ResponseWithContent(Scenario scenario) throws Exception
|
||||
{
|
||||
final String paramName1 = "a";
|
||||
final String paramName2 = "b";
|
||||
String paramName1 = "a";
|
||||
String paramName2 = "b";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -278,8 +279,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_POST_WithParameters(Scenario scenario) throws Exception
|
||||
{
|
||||
final String paramName = "a";
|
||||
final String paramValue = "\u20AC";
|
||||
String paramName = "a";
|
||||
String paramValue = "\u20AC";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -310,9 +311,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_PUT_WithParameters(Scenario scenario) throws Exception
|
||||
{
|
||||
final String paramName = "a";
|
||||
final String paramValue = "\u20AC";
|
||||
final String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
|
||||
String paramName = "a";
|
||||
String paramValue = "\u20AC";
|
||||
String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -344,9 +345,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_POST_WithParameters_WithContent(Scenario scenario) throws Exception
|
||||
{
|
||||
final byte[] content = {0, 1, 2, 3};
|
||||
final String paramName = "a";
|
||||
final String paramValue = "\u20AC";
|
||||
byte[] content = {0, 1, 2, 3};
|
||||
String paramName = "a";
|
||||
String paramValue = "\u20AC";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -389,7 +390,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final byte[] content = {0, 1, 2, 3};
|
||||
byte[] content = {0, 1, 2, 3};
|
||||
ContentResponse response = client.POST(scenario.getScheme() + "://localhost:" + connector.getLocalPort())
|
||||
.onRequestContent((request, buffer) ->
|
||||
{
|
||||
|
@ -420,20 +421,18 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final AtomicInteger progress = new AtomicInteger();
|
||||
AtomicInteger progress = new AtomicInteger();
|
||||
ContentResponse response = client.POST(scenario.getScheme() + "://localhost:" + connector.getLocalPort())
|
||||
.onRequestContent((request, buffer) ->
|
||||
{
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
assertEquals(1, bytes.length);
|
||||
buffer.get(bytes);
|
||||
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)
|
||||
.send();
|
||||
.onRequestContent((request, buffer) ->
|
||||
{
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
assertEquals(1, bytes.length);
|
||||
buffer.get(bytes);
|
||||
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)
|
||||
.send();
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(200, response.getStatus());
|
||||
|
@ -448,8 +447,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
client.setMaxConnectionsPerDestination(1);
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CountDownLatch successLatch = new CountDownLatch(2);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch successLatch = new CountDownLatch(2);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestBegin(request ->
|
||||
|
@ -509,7 +508,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
try (StacklessLogging stackless = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class))
|
||||
{
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.path("/one")
|
||||
|
@ -568,10 +567,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
}
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(3);
|
||||
final AtomicLong exchangeTime = new AtomicLong();
|
||||
final AtomicLong requestTime = new AtomicLong();
|
||||
final AtomicLong responseTime = new AtomicLong();
|
||||
CountDownLatch latch = new CountDownLatch(3);
|
||||
AtomicLong exchangeTime = new AtomicLong();
|
||||
AtomicLong requestTime = new AtomicLong();
|
||||
AtomicLong responseTime = new AtomicLong();
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.file(file)
|
||||
|
@ -623,7 +622,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
// The second ByteBuffer set to null will throw an exception
|
||||
|
@ -678,14 +677,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final String host = "localhost";
|
||||
final int port = connector.getLocalPort();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestBegin(request ->
|
||||
.onRequestBegin(r ->
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(r);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
connectionPool.getActiveConnections().iterator().next().close();
|
||||
})
|
||||
|
@ -706,7 +705,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review
|
||||
public void test_Request_IdleTimeout(Scenario scenario) throws Exception
|
||||
{
|
||||
final long idleTimeout = 1000;
|
||||
long idleTimeout = 1000;
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -724,8 +723,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final String host = "localhost";
|
||||
final int port = connector.getLocalPort();
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
assertThrows(TimeoutException.class, () ->
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
|
@ -763,7 +762,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testHeaderProcessing(Scenario scenario) throws Exception
|
||||
{
|
||||
final String headerName = "X-Header-Test";
|
||||
String headerName = "X-Header-Test";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -792,7 +791,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
int count = 10;
|
||||
final CountDownLatch latch = new CountDownLatch(count);
|
||||
CountDownLatch latch = new CountDownLatch(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
|
@ -821,7 +820,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void test_HEAD_With_ResponseContentLength(Scenario scenario) throws Exception
|
||||
{
|
||||
final int length = 1024;
|
||||
int length = 1024;
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -870,7 +869,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
client.newRequest(host, port)
|
||||
.send(result ->
|
||||
{
|
||||
|
@ -926,7 +925,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testCustomUserAgent(Scenario scenario) throws Exception
|
||||
{
|
||||
final String userAgent = "Test/1.0";
|
||||
String userAgent = "Test/1.0";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -1009,7 +1008,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Request.Listener listener = new Request.Listener()
|
||||
{
|
||||
@Override
|
||||
|
@ -1081,8 +1080,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
final AtomicInteger counter = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Response.Listener listener = new Response.Listener()
|
||||
{
|
||||
@Override
|
||||
|
@ -1161,7 +1160,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void setOnCompleteCallbackWithBlockingSend(Scenario scenario) throws Exception
|
||||
{
|
||||
final byte[] content = new byte[512];
|
||||
byte[] content = new byte[512];
|
||||
new Random().nextBytes(content);
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
|
@ -1173,7 +1172,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final Exchanger<Response> ex = new Exchanger<>();
|
||||
Exchanger<Response> ex = new Exchanger<>();
|
||||
BufferingResponseListener listener = new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
|
@ -1204,7 +1203,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testCustomHostHeader(Scenario scenario) throws Exception
|
||||
{
|
||||
final String host = "localhost";
|
||||
String host = "localhost";
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -1266,18 +1265,17 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
long timeout = 5000;
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.version(HttpVersion.HTTP_1_0)
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
|
||||
.timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
FuturePromise<Connection> promise = new FuturePromise<>();
|
||||
Destination destination = client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
Destination destination = client.resolveDestination(request);
|
||||
destination.newConnection(promise);
|
||||
try (Connection connection = promise.get(5, TimeUnit.SECONDS))
|
||||
{
|
||||
long timeout = 5000;
|
||||
Request request = client.newRequest(destination.getHost(), destination.getPort())
|
||||
.scheme(destination.getScheme())
|
||||
.version(HttpVersion.HTTP_1_0)
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString())
|
||||
.timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
ContentResponse response = listener.get(2 * timeout, TimeUnit.MILLISECONDS);
|
||||
|
@ -1312,7 +1310,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testLongPollIsAbortedWhenClientIsStopped(Scenario scenario) throws Exception
|
||||
{
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -1324,7 +1322,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.send(result ->
|
||||
|
@ -1343,7 +1341,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testSmallContentDelimitedByEOFWithSlowRequestHTTP10(Scenario scenario) throws Exception
|
||||
public void testSmallContentDelimitedByEOFWithSlowRequestHTTP10(Scenario scenario)
|
||||
{
|
||||
Assumptions.assumeTrue(HttpScheme.HTTP.is(scenario.getScheme()));
|
||||
|
||||
|
@ -1356,7 +1354,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(NonSslScenarioProvider.class)
|
||||
public void testBigContentDelimitedByEOFWithSlowRequestHTTP10(Scenario scenario) throws Exception
|
||||
public void testBigContentDelimitedByEOFWithSlowRequestHTTP10(Scenario scenario)
|
||||
{
|
||||
ExecutionException e = assertThrows(ExecutionException.class, () ->
|
||||
testContentDelimitedByEOFWithSlowRequest(scenario, HttpVersion.HTTP_1_0, 128 * 1024));
|
||||
|
@ -1379,7 +1377,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
testContentDelimitedByEOFWithSlowRequest(scenario, HttpVersion.HTTP_1_1, 128 * 1024);
|
||||
}
|
||||
|
||||
private void testContentDelimitedByEOFWithSlowRequest(final Scenario scenario, final HttpVersion version, int length) throws Exception
|
||||
private void testContentDelimitedByEOFWithSlowRequest(Scenario scenario, HttpVersion version, int length) throws Exception
|
||||
{
|
||||
// This test is crafted in a way that the response completes before the request is fully written.
|
||||
// With SSL, the response coming down will close the SSLEngine so it would not be possible to
|
||||
|
@ -1388,7 +1386,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
// This is a limit of Java's SSL implementation that does not allow half closes.
|
||||
Assumptions.assumeTrue(HttpScheme.HTTP.is(scenario.getScheme()));
|
||||
|
||||
final byte[] data = new byte[length];
|
||||
byte[] data = new byte[length];
|
||||
new Random().nextBytes(data);
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
|
@ -1424,8 +1422,8 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testRequestRetries(Scenario scenario) throws Exception
|
||||
{
|
||||
final int maxRetries = 3;
|
||||
final AtomicInteger requests = new AtomicInteger();
|
||||
int maxRetries = 3;
|
||||
AtomicInteger requests = new AtomicInteger();
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
|
@ -1438,7 +1436,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
new RetryListener(client, scenario.getScheme(), "localhost", connector.getLocalPort(), maxRetries)
|
||||
{
|
||||
@Override
|
||||
|
@ -1466,9 +1464,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final AtomicReference<Callback> callbackRef = new AtomicReference<>();
|
||||
final CountDownLatch contentLatch = new CountDownLatch(1);
|
||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
AtomicReference<Callback> callbackRef = new AtomicReference<>();
|
||||
CountDownLatch contentLatch = new CountDownLatch(1);
|
||||
CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.send(new Response.Listener.Adapter()
|
||||
|
@ -1514,7 +1512,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
AtomicBoolean open = new AtomicBoolean();
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
clientConnector.setSslContextFactory(scenario.newClientSslContextFactory());
|
||||
client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)
|
||||
|
@ -1535,7 +1533,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
});
|
||||
client.start();
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestBegin(request ->
|
||||
|
@ -1567,7 +1565,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
.method(HttpMethod.CONNECT)
|
||||
.version(HttpVersion.HTTP_1_0);
|
||||
FuturePromise<Connection> promise = new FuturePromise<>();
|
||||
client.getDestination("http", host, port).newConnection(promise);
|
||||
client.resolveDestination(request).newConnection(promise);
|
||||
Connection connection = promise.get(5, TimeUnit.SECONDS);
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
|
|
|
@ -45,7 +45,7 @@ import org.junit.jupiter.api.Test;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HttpClientUploadDuringServerShutdown
|
||||
public class HttpClientUploadDuringServerShutdownTest
|
||||
{
|
||||
/**
|
||||
* A server used in conjunction with {@link ClientSide}.
|
||||
|
@ -117,8 +117,8 @@ public class HttpClientUploadDuringServerShutdown
|
|||
{
|
||||
int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024);
|
||||
client.newRequest("localhost", 8888)
|
||||
.content(new BytesContentProvider(new byte[length]))
|
||||
.send(result -> latch.countDown());
|
||||
.content(new BytesContentProvider(new byte[length]))
|
||||
.send(result -> latch.countDown());
|
||||
long sleep = 1 + random.nextInt(10);
|
||||
TimeUnit.MILLISECONDS.sleep(sleep);
|
||||
}
|
||||
|
@ -229,10 +229,10 @@ public class HttpClientUploadDuringServerShutdown
|
|||
// is being closed is used to send the request.
|
||||
assertTrue(sendLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
var request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.timeout(10, TimeUnit.SECONDS)
|
||||
.onRequestBegin(request ->
|
||||
.onRequestBegin(r ->
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -243,12 +243,12 @@ public class HttpClientUploadDuringServerShutdown
|
|||
{
|
||||
x.printStackTrace();
|
||||
}
|
||||
})
|
||||
.send(result -> completeLatch.countDown());
|
||||
});
|
||||
request.send(result -> completeLatch.countDown());
|
||||
|
||||
assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool pool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, pool.getConnectionCount());
|
||||
assertEquals(0, pool.getIdleConnections().size());
|
|
@ -18,14 +18,12 @@
|
|||
|
||||
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;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
|
@ -67,20 +65,19 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
CountDownLatch headersLatch = new CountDownLatch(1);
|
||||
CountDownLatch successLatch = new CountDownLatch(3);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
final CountDownLatch headersLatch = new CountDownLatch(1);
|
||||
final CountDownLatch successLatch = new CountDownLatch(3);
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestSuccess(request -> successLatch.countDown())
|
||||
request.onRequestSuccess(r -> successLatch.countDown())
|
||||
.onResponseHeaders(response ->
|
||||
{
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
@ -118,18 +115,19 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
CountDownLatch beginLatch = new CountDownLatch(1);
|
||||
CountDownLatch failureLatch = new CountDownLatch(2);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
final CountDownLatch beginLatch = new CountDownLatch(1);
|
||||
final CountDownLatch failureLatch = new CountDownLatch(2);
|
||||
client.newRequest(host, port).scheme(scenario.getScheme()).listener(new Request.Listener.Adapter()
|
||||
request.listener(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
|
@ -143,7 +141,8 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
failureLatch.countDown();
|
||||
}
|
||||
}).send(new Response.Listener.Adapter()
|
||||
})
|
||||
.send(new Response.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
|
@ -170,51 +169,50 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
CountDownLatch successLatch = new CountDownLatch(3);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Queue<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
final CountDownLatch successLatch = new CountDownLatch(3);
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.listener(new Request.Listener.Adapter()
|
||||
request.listener(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
// Remove the host header, this will make the request invalid
|
||||
request.header(HttpHeader.HOST, null);
|
||||
}
|
||||
// Remove the host header, this will make the request invalid
|
||||
request.header(HttpHeader.HOST, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Request request)
|
||||
{
|
||||
successLatch.countDown();
|
||||
}
|
||||
})
|
||||
.send(new Response.Listener.Adapter()
|
||||
@Override
|
||||
public void onSuccess(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
assertEquals(400, response.getStatus());
|
||||
// 400 response also come with a Connection: close,
|
||||
// so the connection is closed and removed
|
||||
successLatch.countDown();
|
||||
}
|
||||
successLatch.countDown();
|
||||
}
|
||||
})
|
||||
.send(new Response.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
assertEquals(400, response.getStatus());
|
||||
// 400 response also come with a Connection: close,
|
||||
// so the connection is closed and removed
|
||||
successLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
assertFalse(result.isFailed());
|
||||
successLatch.countDown();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
assertFalse(result.isFailed());
|
||||
successLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(successLatch.await(30, TimeUnit.SECONDS));
|
||||
|
||||
|
@ -232,65 +230,64 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
CountDownLatch successLatch = new CountDownLatch(3);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
final long delay = 1000;
|
||||
final CountDownLatch successLatch = new CountDownLatch(3);
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.listener(new Request.Listener.Adapter()
|
||||
long delay = 1000;
|
||||
request.listener(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
// Remove the host header, this will make the request invalid
|
||||
request.header(HttpHeader.HOST, null);
|
||||
}
|
||||
// Remove the host header, this will make the request invalid
|
||||
request.header(HttpHeader.HOST, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Request request)
|
||||
{
|
||||
try
|
||||
{
|
||||
TimeUnit.MILLISECONDS.sleep(delay);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Request request)
|
||||
{
|
||||
successLatch.countDown();
|
||||
}
|
||||
})
|
||||
.send(new Response.Listener.Adapter()
|
||||
@Override
|
||||
public void onHeaders(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
try
|
||||
{
|
||||
assertEquals(400, response.getStatus());
|
||||
// 400 response also come with a Connection: close,
|
||||
// so the connection is closed and removed
|
||||
successLatch.countDown();
|
||||
TimeUnit.MILLISECONDS.sleep(delay);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
assertFalse(result.isFailed());
|
||||
successLatch.countDown();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onSuccess(Request request)
|
||||
{
|
||||
successLatch.countDown();
|
||||
}
|
||||
})
|
||||
.send(new Response.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
assertEquals(400, response.getStatus());
|
||||
// 400 response also come with a Connection: close,
|
||||
// so the connection is closed and removed
|
||||
successLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
assertFalse(result.isFailed());
|
||||
successLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(successLatch.await(delay * 30, TimeUnit.MILLISECONDS));
|
||||
|
||||
|
@ -306,21 +303,20 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
server.stop();
|
||||
|
||||
final CountDownLatch failureLatch = new CountDownLatch(2);
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestFailure((request, failure) -> failureLatch.countDown())
|
||||
CountDownLatch failureLatch = new CountDownLatch(2);
|
||||
request.onRequestFailure((r, x) -> failureLatch.countDown())
|
||||
.send(result ->
|
||||
{
|
||||
assertTrue(result.isFailed());
|
||||
|
@ -340,7 +336,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
response.setHeader("Connection", "close");
|
||||
baseRequest.setHandled(true);
|
||||
|
@ -349,29 +345,28 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.send(new Response.Listener.Adapter()
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
request.send(new Response.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
assertFalse(result.isFailed());
|
||||
assertEquals(0, idleConnections.size());
|
||||
assertEquals(0, activeConnections.size());
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
assertFalse(result.isFailed());
|
||||
assertEquals(0, idleConnections.size());
|
||||
assertEquals(0, activeConnections.size());
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(latch.await(30, TimeUnit.SECONDS));
|
||||
|
||||
|
@ -388,7 +383,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
response.setHeader("Connection", "close");
|
||||
baseRequest.setHandled(true);
|
||||
|
@ -398,23 +393,22 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
Log.getLogger(HttpConnection.class).info("Expecting java.lang.IllegalStateException: HttpParser{s=CLOSED,...");
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(16 * 1024 * 1024);
|
||||
Arrays.fill(buffer.array(), (byte)'x');
|
||||
client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.content(new ByteBufferContentProvider(buffer))
|
||||
request.content(new ByteBufferContentProvider(buffer))
|
||||
.send(new Response.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -446,19 +440,17 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
ContentResponse response = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.timeout(30, TimeUnit.SECONDS)
|
||||
.send();
|
||||
ContentResponse response = request.timeout(30, TimeUnit.SECONDS).send();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
|
@ -479,18 +471,18 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), host, port);
|
||||
Request request = client.newRequest(host, port).scheme(scenario.getScheme());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
|
||||
final Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
Collection<Connection> idleConnections = connectionPool.getIdleConnections();
|
||||
assertEquals(0, idleConnections.size());
|
||||
|
||||
final Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
Collection<Connection> activeConnections = connectionPool.getActiveConnections();
|
||||
assertEquals(0, activeConnections.size());
|
||||
|
||||
client.setStrictEventOrdering(false);
|
||||
ContentResponse response = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
ContentResponse response = request
|
||||
.onResponseBegin(response1 ->
|
||||
{
|
||||
// Simulate a HTTP 1.0 response has been received.
|
||||
|
|
|
@ -55,18 +55,17 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
|
||||
Exception failure = new Exception("oops");
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.timeout(5, TimeUnit.SECONDS);
|
||||
request.timeout(5, TimeUnit.SECONDS);
|
||||
request.abort(failure);
|
||||
request.send();
|
||||
});
|
||||
|
||||
assertSame(failure, x.getCause());
|
||||
// Make sure the pool is in a sane state.
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(1, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -79,32 +78,29 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
start(scenario, new EmptyServerHandler());
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final AtomicBoolean aborted = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicBoolean begin = new AtomicBoolean();
|
||||
Throwable cause = new Exception();
|
||||
AtomicBoolean aborted = new AtomicBoolean();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean begin = new AtomicBoolean();
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.listener(new Request.Listener.Adapter()
|
||||
request.listener(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onQueued(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onQueued(Request request)
|
||||
{
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
}
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
begin.set(true);
|
||||
}
|
||||
})
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
begin.set(true);
|
||||
}
|
||||
}).timeout(5, TimeUnit.SECONDS).send();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
|
@ -112,7 +108,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
assertSame(cause, x.getCause());
|
||||
assertFalse(begin.get());
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -130,34 +126,31 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CountDownLatch committed = new CountDownLatch(1);
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.listener(new Request.Listener.Adapter()
|
||||
request.listener(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
}
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommit(Request request)
|
||||
{
|
||||
committed.countDown();
|
||||
}
|
||||
})
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
@Override
|
||||
public void onCommit(Request request)
|
||||
{
|
||||
committed.countDown();
|
||||
}
|
||||
}).timeout(5, TimeUnit.SECONDS).send();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
if (aborted.get())
|
||||
assertSame(cause, x.getCause());
|
||||
assertFalse(committed.await(1, TimeUnit.SECONDS));
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -175,34 +168,31 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CountDownLatch committed = new CountDownLatch(1);
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.listener(new Request.Listener.Adapter()
|
||||
request.listener(new Request.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Request request)
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(Request request)
|
||||
{
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
}
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommit(Request request)
|
||||
{
|
||||
committed.countDown();
|
||||
}
|
||||
})
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
@Override
|
||||
public void onCommit(Request request)
|
||||
{
|
||||
committed.countDown();
|
||||
}
|
||||
}).timeout(5, TimeUnit.SECONDS).send();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
if (aborted.get())
|
||||
assertSame(cause, x.getCause());
|
||||
assertFalse(committed.await(1, TimeUnit.SECONDS));
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -219,26 +209,24 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
// A) the request is failed before the response arrived
|
||||
// B) the request is failed after the response arrived
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final AtomicBoolean aborted = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Throwable cause = new Exception();
|
||||
AtomicBoolean aborted = new AtomicBoolean();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestCommit(request ->
|
||||
{
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
})
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
request.onRequestCommit(r ->
|
||||
{
|
||||
aborted.set(r.abort(cause));
|
||||
latch.countDown();
|
||||
}).timeout(5, TimeUnit.SECONDS).send();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
if (aborted.get())
|
||||
assertSame(cause, x.getCause());
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -249,11 +237,11 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testAbortOnCommitWithContent(Scenario scenario) throws Exception
|
||||
{
|
||||
final AtomicReference<IOException> failure = new AtomicReference<>();
|
||||
AtomicReference<IOException> failure = new AtomicReference<>();
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -269,35 +257,31 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final AtomicBoolean aborted = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Throwable cause = new Exception();
|
||||
AtomicBoolean aborted = new AtomicBoolean();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestCommit(request ->
|
||||
request.onRequestCommit(r ->
|
||||
{
|
||||
aborted.set(r.abort(cause));
|
||||
latch.countDown();
|
||||
}).content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
})
|
||||
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
})
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
return -1;
|
||||
}
|
||||
}).timeout(5, TimeUnit.SECONDS).send();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
if (aborted.get())
|
||||
assertSame(cause, x.getCause());
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -314,7 +298,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -328,28 +312,25 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final AtomicBoolean aborted = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Throwable cause = new Exception();
|
||||
AtomicBoolean aborted = new AtomicBoolean();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort()).scheme(scenario.getScheme());
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.onRequestContent((request, content) ->
|
||||
request.onRequestContent((r, c) ->
|
||||
{
|
||||
aborted.set(r.abort(cause));
|
||||
latch.countDown();
|
||||
}).content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
aborted.set(request.abort(cause));
|
||||
latch.countDown();
|
||||
})
|
||||
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
})
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
return -1;
|
||||
}
|
||||
}).timeout(5, TimeUnit.SECONDS).send();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
if (aborted.get())
|
||||
|
@ -357,7 +338,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
|
||||
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -369,11 +350,11 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
@ArgumentsSource(ScenarioProvider.class)
|
||||
public void testInterrupt(Scenario scenario) throws Exception
|
||||
{
|
||||
final long delay = 1000;
|
||||
long delay = 1000;
|
||||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -389,10 +370,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
});
|
||||
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.timeout(3 * delay, TimeUnit.MILLISECONDS)
|
||||
.scheme(scenario.getScheme());
|
||||
.timeout(3 * delay, TimeUnit.MILLISECONDS)
|
||||
.scheme(scenario.getScheme());
|
||||
|
||||
final Thread thread = Thread.currentThread();
|
||||
Thread thread = Thread.currentThread();
|
||||
new Thread(() ->
|
||||
{
|
||||
try
|
||||
|
@ -406,7 +387,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
}).start();
|
||||
|
||||
assertThrows(InterruptedException.class, () -> request.send());
|
||||
assertThrows(InterruptedException.class, request::send);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -417,7 +398,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -432,13 +413,13 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
});
|
||||
|
||||
final Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.timeout(3 * delay, TimeUnit.MILLISECONDS)
|
||||
.scheme(scenario.getScheme());
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.timeout(3 * delay, TimeUnit.MILLISECONDS)
|
||||
.scheme(scenario.getScheme());
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final AtomicBoolean aborted = new AtomicBoolean();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Throwable cause = new Exception();
|
||||
AtomicBoolean aborted = new AtomicBoolean();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
new Thread(() ->
|
||||
{
|
||||
try
|
||||
|
@ -464,7 +445,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
assertSame(cause, x.getCause());
|
||||
}
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scenario.getScheme(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getActiveConnections().size());
|
||||
|
@ -479,7 +460,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -497,8 +478,8 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
final Throwable cause = new Exception();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.timeout(3 * delay, TimeUnit.MILLISECONDS);
|
||||
.scheme(scenario.getScheme())
|
||||
.timeout(3 * delay, TimeUnit.MILLISECONDS);
|
||||
request.send(result ->
|
||||
{
|
||||
assertTrue(result.isFailed());
|
||||
|
@ -520,7 +501,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
start(scenario, new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
if (!"/done".equals(request.getRequestURI()))
|
||||
|
@ -555,10 +536,10 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
ExecutionException e = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scenario.getScheme())
|
||||
.path("/redirect")
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
.scheme(scenario.getScheme())
|
||||
.path("/redirect")
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
});
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
if (aborted.get())
|
||||
|
|
|
@ -147,7 +147,7 @@ public class ServerConnectionCloseTest
|
|||
Thread.sleep(1000);
|
||||
|
||||
// Connection should have been removed from pool.
|
||||
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getIdleConnectionCount());
|
||||
|
|
|
@ -172,7 +172,7 @@ public class TLSServerConnectionCloseTest
|
|||
Thread.sleep(1000);
|
||||
|
||||
// Connection should have been removed from pool.
|
||||
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(0, connectionPool.getConnectionCount());
|
||||
assertEquals(0, connectionPool.getIdleConnectionCount());
|
||||
|
|
|
@ -153,13 +153,13 @@ public class Usage
|
|||
HttpClient client = new HttpClient();
|
||||
client.start();
|
||||
|
||||
Request request = client.newRequest("localhost", 8080);
|
||||
|
||||
// Create an explicit connection, and use try-with-resources to manage it
|
||||
FuturePromise<Connection> futureConnection = new FuturePromise<>();
|
||||
client.getDestination("http", "localhost", 8080).newConnection(futureConnection);
|
||||
client.resolveDestination(request).newConnection(futureConnection);
|
||||
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
|
||||
{
|
||||
Request request = client.newRequest("localhost", 8080);
|
||||
|
||||
// Asynchronous send but using FutureResponseListener
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
|
@ -293,15 +293,8 @@ public class Usage
|
|||
try (OutputStream output = content.getOutputStream())
|
||||
{
|
||||
client.newRequest("localhost", 8080)
|
||||
.content(content)
|
||||
.send(new Response.CompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
assertEquals(200, result.getResponse().getStatus());
|
||||
}
|
||||
});
|
||||
.content(content)
|
||||
.send(result -> assertEquals(200, result.getResponse().getStatus()));
|
||||
|
||||
output.write(new byte[1024]);
|
||||
output.write(new byte[512]);
|
||||
|
|
|
@ -260,28 +260,27 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest
|
|||
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
Destination destinationBefore = client.getDestination(scenario.getScheme(), host, port);
|
||||
|
||||
ContentResponse response = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.send();
|
||||
Request request = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
Destination destinationBefore = client.resolveDestination(request);
|
||||
ContentResponse response = request.send();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
Destination destinationAfter = client.getDestination(scenario.getScheme(), host, port);
|
||||
Destination destinationAfter = client.resolveDestination(request);
|
||||
assertSame(destinationBefore, destinationAfter);
|
||||
|
||||
client.setRemoveIdleDestinations(true);
|
||||
|
||||
response = client.newRequest(host, port)
|
||||
request = client.newRequest(host, port)
|
||||
.scheme(scenario.getScheme())
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
|
||||
.send();
|
||||
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
response = request.send();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
destinationAfter = client.getDestination(scenario.getScheme(), host, port);
|
||||
destinationAfter = client.resolveDestination(request);
|
||||
assertNotSame(destinationBefore, destinationAfter);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||
<!-- Bind the jetty-web-decorate.xml to every deployed webapp -->
|
||||
<Ref refid="DeploymentManager">
|
||||
<Call name="addLifeCycleBinding">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.deploy.bindings.GlobalWebappConfigBinding">
|
||||
<Set name="jettyXml"><Property name="jetty.home" default="." />/etc/jetty-web-decorate.xml
|
||||
</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Ref>
|
||||
</Configure>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||
|
||||
<Configure class="org.eclipse.jetty.webapp.WebAppContext" id="context">
|
||||
|
||||
<Get class="org.eclipse.jetty.webapp.DecoratingListener" name="DECORATOR_ATTRIBUTE" id="decoratorAttribute"/>
|
||||
|
||||
<!-- Add the DecoratingListener to the webapp to look for dynamic decorators -->
|
||||
<Call name="addEventListener">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.webapp.DecoratingListener">
|
||||
<Arg><Ref refid="context" /></Arg>
|
||||
<Arg type="String"><Ref refid="decoratorAttribute"/></Arg>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
</Configure>
|
|
@ -0,0 +1,14 @@
|
|||
# DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[description]
|
||||
Jetty setup to support Decoration of Listeners, Filters and Servlets within a deployed webapp.
|
||||
This module uses the DecoratingListener to register an object set as a context attribute
|
||||
as a dynamic decorator. This module sets the "org.eclipse.jetty.webapp.DecoratingListener"
|
||||
context attribute with the name of the context attribute that will be listened to.
|
||||
By default the attribute is "org.eclipse.jetty.webapp.decorator".
|
||||
|
||||
[depend]
|
||||
deploy
|
||||
|
||||
[xml]
|
||||
etc/jetty-decorate.xml
|
|
@ -30,13 +30,13 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<h2>examples ...</h2>
|
||||
<h2>tests ...</h2>
|
||||
<ul>
|
||||
<li><a href="/test/">Test Jetty Webapp</a></li>
|
||||
<li><a href="/async-rest/">Async Rest</a></li>
|
||||
<li><a href="/test-spec/">Servlet 3.1 Test</a></li>
|
||||
<li><a href="/test-jaas/">JAAS Test</a></li>
|
||||
<li><a href="/test-jndi/">JNDI Test</a></li>
|
||||
<li><a href="/test-spec/">Servlet 3.1 Test</a></li>
|
||||
<li><a href="/async-rest/">Async Rest</a></li>
|
||||
<li><a href="/oldContextPath/">Redirected Context</a></li>
|
||||
</ul>
|
||||
</td>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.fcgi.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.client.AbstractConnectorHttpClientTransport;
|
||||
|
@ -26,6 +27,8 @@ import org.eclipse.jetty.client.DuplexConnectionPool;
|
|||
import org.eclipse.jetty.client.DuplexHttpDestination;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.fcgi.FCGI;
|
||||
|
@ -71,6 +74,12 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran
|
|||
return scriptRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
{
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(List.of("fastcgi/1.1"), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.eclipse.jetty.fcgi.generator.Generator;
|
|||
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.server.HttpTransport;
|
||||
|
@ -50,10 +51,13 @@ public class HttpTransportOverFCGI implements HttpTransport
|
|||
}
|
||||
|
||||
@Override
|
||||
public void send(MetaData.Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
if (info != null)
|
||||
commit(info, head, content, lastContent, callback);
|
||||
boolean head = HttpMethod.HEAD.is(request.getMethod());
|
||||
if (response != null)
|
||||
{
|
||||
commit(response, head, content, lastContent, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (head)
|
||||
|
|
|
@ -689,8 +689,8 @@
|
|||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||
<artifactId>cdi-2</artifactId>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-cdi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
# ---------------------------------------
|
||||
# Module: session-store-infinispan-embedded
|
||||
# Enables session data store in a local Infinispan cache
|
||||
# ---------------------------------------
|
||||
--module=session-store-infinispan-embedded
|
||||
|
||||
#jetty.session.gracePeriod.seconds=3600
|
||||
#jetty.session.savePeriod.seconds=0
|
||||
|
|
@ -133,6 +133,7 @@ public enum HttpHeader
|
|||
C_AUTHORITY(":authority"),
|
||||
C_PATH(":path"),
|
||||
C_STATUS(":status"),
|
||||
C_PROTOCOL(":protocol"),
|
||||
|
||||
UNKNOWN("::UNKNOWN::");
|
||||
|
||||
|
|
|
@ -161,7 +161,8 @@ public class HttpURI
|
|||
_host = host;
|
||||
_port = port;
|
||||
|
||||
parse(State.PATH, pathQuery);
|
||||
if (pathQuery != null)
|
||||
parse(State.PATH, pathQuery);
|
||||
}
|
||||
|
||||
public void parse(String uri)
|
||||
|
|
|
@ -155,25 +155,19 @@ public class MetaData implements Iterable<HttpField>
|
|||
|
||||
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields)
|
||||
{
|
||||
this(method, new HttpURI(scheme == null ? null : scheme.asString(),
|
||||
hostPort == null ? null : hostPort.getHost(),
|
||||
hostPort == null ? -1 : hostPort.getPort(),
|
||||
uri), version, fields);
|
||||
this(method, scheme, hostPort, uri, version, fields, Long.MIN_VALUE);
|
||||
}
|
||||
|
||||
public Request(String method, HttpScheme scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
|
||||
{
|
||||
this(method, new HttpURI(scheme == null ? null : scheme.asString(),
|
||||
hostPort == null ? null : hostPort.getHost(),
|
||||
hostPort == null ? -1 : hostPort.getPort(),
|
||||
uri), version, fields, contentLength);
|
||||
this(method, scheme == null ? null : scheme.asString(), hostPort, uri, version, fields, contentLength);
|
||||
}
|
||||
|
||||
public Request(String method, String scheme, HostPortHttpField hostPort, String uri, HttpVersion version, HttpFields fields, long contentLength)
|
||||
{
|
||||
this(method, new HttpURI(scheme,
|
||||
hostPort == null ? null : hostPort.getHost(),
|
||||
hostPort == null ? -1 : hostPort.getPort(),
|
||||
hostPort == null ? null : hostPort.getHost(),
|
||||
hostPort == null ? -1 : hostPort.getPort(),
|
||||
uri), version, fields, contentLength);
|
||||
}
|
||||
|
||||
|
@ -221,14 +215,6 @@ public class MetaData implements Iterable<HttpField>
|
|||
return _uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the HTTP URI in string form
|
||||
*/
|
||||
public String getURIString()
|
||||
{
|
||||
return _uri == null ? null : _uri.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri the HTTP URI to set
|
||||
*/
|
||||
|
@ -237,12 +223,49 @@ public class MetaData implements Iterable<HttpField>
|
|||
_uri = uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the HTTP URI in string form
|
||||
*/
|
||||
public String getURIString()
|
||||
{
|
||||
return _uri == null ? null : _uri.toString();
|
||||
}
|
||||
|
||||
public String getProtocol()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
HttpFields fields = getFields();
|
||||
return String.format("%s{u=%s,%s,h=%d,cl=%d}",
|
||||
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength());
|
||||
return String.format("%s{u=%s,%s,h=%d,cl=%d,p=%s}",
|
||||
getMethod(), getURI(), getHttpVersion(), fields == null ? -1 : fields.size(), getContentLength(), getProtocol());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConnectRequest extends Request
|
||||
{
|
||||
private String _protocol;
|
||||
|
||||
public ConnectRequest(HttpScheme scheme, HostPortHttpField authority, String path, HttpFields fields, String protocol)
|
||||
{
|
||||
super(HttpMethod.CONNECT.asString(), scheme, authority, path, HttpVersion.HTTP_2, fields, Long.MIN_VALUE);
|
||||
_protocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProtocol()
|
||||
{
|
||||
return _protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle()
|
||||
{
|
||||
super.recycle();
|
||||
_protocol = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
private int maxConcurrentPushedStreams = 32;
|
||||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
|
||||
private long streamIdleTimeout;
|
||||
|
||||
public HTTP2Client()
|
||||
{
|
||||
|
@ -194,6 +195,17 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
connector.setIdleTimeout(Duration.ofMillis(idleTimeout));
|
||||
}
|
||||
|
||||
@ManagedAttribute("The stream idle timeout in milliseconds")
|
||||
public long getStreamIdleTimeout()
|
||||
{
|
||||
return streamIdleTimeout;
|
||||
}
|
||||
|
||||
public void setStreamIdleTimeout(long streamIdleTimeout)
|
||||
{
|
||||
this.streamIdleTimeout = streamIdleTimeout;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The connect timeout in milliseconds")
|
||||
public long getConnectTimeout()
|
||||
{
|
||||
|
@ -323,7 +335,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
public void connect(SocketAddress address, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
context = contextFrom(factory, listener, promise, context);
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
connector.connect(address, context);
|
||||
}
|
||||
|
||||
|
@ -336,7 +348,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
public void accept(SocketChannel channel, ClientConnectionFactory factory, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
Map<String, Object> context = contextFrom(factory, listener, promise, null);
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, new Promise.Wrapper<>(promise));
|
||||
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise);
|
||||
connector.accept(channel, context);
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,9 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
|
|||
final FlowControlStrategy flowControl = client.getFlowControlStrategyFactory().newFlowControlStrategy();
|
||||
final HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, flowControl);
|
||||
session.setMaxRemoteStreams(client.getMaxConcurrentPushedStreams());
|
||||
long streamIdleTimeout = client.getStreamIdleTimeout();
|
||||
if (streamIdleTimeout > 0)
|
||||
session.setStreamIdleTimeout(streamIdleTimeout);
|
||||
|
||||
final Parser parser = new Parser(byteBufferPool, session, 4096, 8192);
|
||||
parser.setMaxFrameLength(client.getMaxFrameLength());
|
||||
|
|
|
@ -145,7 +145,7 @@ public class HTTP2ClientSession extends HTTP2Session
|
|||
}
|
||||
else
|
||||
{
|
||||
IStream pushStream = createRemoteStream(pushStreamId);
|
||||
IStream pushStream = createRemoteStream(pushStreamId, frame.getMetaData());
|
||||
if (pushStream != null)
|
||||
{
|
||||
pushStream.process(frame, Callback.NOOP);
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.http2.client;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.http.HostPortHttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ConnectTunnelTest extends AbstractTest
|
||||
{
|
||||
@Test
|
||||
public void testCONNECT() throws Exception
|
||||
{
|
||||
start(new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
// Verifies that the CONNECT request is well formed.
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
assertEquals(HttpMethod.CONNECT.asString(), request.getMethod());
|
||||
HttpURI uri = request.getURI();
|
||||
assertNull(uri.getScheme());
|
||||
assertNull(uri.getPath());
|
||||
assertNotNull(uri.getAuthority());
|
||||
return new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
stream.data(frame, callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Session client = newClient(new Session.Listener.Adapter());
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8);
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
String authority = host + ":" + port;
|
||||
MetaData.Request request = new MetaData.Request(HttpMethod.CONNECT.asString(), null, new HostPortHttpField(authority), null, HttpVersion.HTTP_2, new HttpFields());
|
||||
FuturePromise<Stream> streamPromise = new FuturePromise<>();
|
||||
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
if (frame.isEndStream())
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
|
||||
ByteBuffer data = ByteBuffer.wrap(bytes);
|
||||
stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP);
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTWithProtocol() throws Exception
|
||||
{
|
||||
start(new ServerSessionListener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
// Verifies that the CONNECT request is well formed.
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
assertEquals(HttpMethod.CONNECT.asString(), request.getMethod());
|
||||
HttpURI uri = request.getURI();
|
||||
assertNotNull(uri.getScheme());
|
||||
assertNotNull(uri.getPath());
|
||||
assertNotNull(uri.getAuthority());
|
||||
assertNotNull(request.getProtocol());
|
||||
return new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
stream.data(frame, callback);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Session client = newClient(new Session.Listener.Adapter());
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
byte[] bytes = "HELLO".getBytes(StandardCharsets.UTF_8);
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
String authority = host + ":" + port;
|
||||
MetaData.Request request = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(authority), "/", new HttpFields(), "websocket");
|
||||
FuturePromise<Stream> streamPromise = new FuturePromise<>();
|
||||
client.newStream(new HeadersFrame(request, null, false), streamPromise, new Stream.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
if (frame.isEndStream())
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
|
||||
ByteBuffer data = ByteBuffer.wrap(bytes);
|
||||
stream.data(new DataFrame(stream.getId(), data, true), Callback.NOOP);
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.http2;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
/**
|
||||
* <p>A HTTP/2 specific handler of events for normal and tunneled exchanges.</p>
|
||||
*/
|
||||
public interface HTTP2Channel
|
||||
{
|
||||
/**
|
||||
* <p>A client specific handler for events that happen after
|
||||
* a {@code HEADERS} response frame is received.</p>
|
||||
* <p>{@code DATA} frames may be handled as response content
|
||||
* or as opaque tunnelled data.</p>
|
||||
*/
|
||||
public interface Client
|
||||
{
|
||||
public void onData(DataFrame frame, Callback callback);
|
||||
|
||||
public boolean onTimeout(Throwable failure);
|
||||
|
||||
public void onFailure(Throwable failure, Callback callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A server specific handler for events that happen after
|
||||
* a {@code HEADERS} request frame is received.</p>
|
||||
* <p>{@code DATA} frames may be handled as request content
|
||||
* or as opaque tunnelled data.</p>
|
||||
*/
|
||||
public interface Server
|
||||
{
|
||||
public Runnable onData(DataFrame frame, Callback callback);
|
||||
|
||||
public Runnable onTrailer(HeadersFrame frame);
|
||||
|
||||
public boolean onTimeout(Throwable failure, Consumer<Runnable> consumer);
|
||||
|
||||
public Runnable onFailure(Throwable failure, Callback callback);
|
||||
|
||||
public boolean isIdle();
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.http2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
|
@ -33,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
|
@ -43,6 +45,7 @@ import org.eclipse.jetty.http2.frames.FrameType;
|
|||
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PingFrame;
|
||||
import org.eclipse.jetty.http2.frames.PrefaceFrame;
|
||||
import org.eclipse.jetty.http2.frames.PriorityFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
|
@ -561,6 +564,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
{
|
||||
// Synchronization is necessary to atomically create
|
||||
// the stream id and enqueue the frame to be sent.
|
||||
IStream stream;
|
||||
boolean queued;
|
||||
synchronized (this)
|
||||
{
|
||||
|
@ -573,12 +577,13 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
priority.getWeight(), priority.isExclusive());
|
||||
frame = new HeadersFrame(streamId, frame.getMetaData(), priority, frame.isEndStream());
|
||||
}
|
||||
IStream stream = createLocalStream(streamId);
|
||||
stream = createLocalStream(streamId, (MetaData.Request)frame.getMetaData());
|
||||
stream.setListener(listener);
|
||||
|
||||
ControlEntry entry = new ControlEntry(frame, stream, new StreamPromiseCallback(promise, stream));
|
||||
queued = flusher.append(entry);
|
||||
}
|
||||
stream.process(new PrefaceFrame(), Callback.NOOP);
|
||||
// Iterate outside the synchronized block.
|
||||
if (queued)
|
||||
flusher.iterate();
|
||||
|
@ -589,9 +594,9 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
protected IStream newStream(int streamId, boolean local)
|
||||
protected IStream newStream(int streamId, MetaData.Request request, boolean local)
|
||||
{
|
||||
return new HTTP2Stream(scheduler, this, streamId, local);
|
||||
return new HTTP2Stream(scheduler, this, streamId, request, local);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -622,7 +627,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
int streamId = localStreamIds.getAndAdd(2);
|
||||
frame = new PushPromiseFrame(frame.getStreamId(), streamId, frame.getMetaData());
|
||||
|
||||
IStream pushStream = createLocalStream(streamId);
|
||||
IStream pushStream = createLocalStream(streamId, frame.getMetaData());
|
||||
pushStream.setListener(listener);
|
||||
|
||||
ControlEntry entry = new ControlEntry(frame, pushStream, new StreamPromiseCallback(promise, pushStream));
|
||||
|
@ -778,7 +783,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
protected IStream createLocalStream(int streamId)
|
||||
protected IStream createLocalStream(int streamId, MetaData.Request request)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
@ -791,7 +796,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
break;
|
||||
}
|
||||
|
||||
IStream stream = newStream(streamId, true);
|
||||
IStream stream = newStream(streamId, request, true);
|
||||
if (streams.putIfAbsent(streamId, stream) == null)
|
||||
{
|
||||
stream.setIdleTimeout(getStreamIdleTimeout());
|
||||
|
@ -807,7 +812,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
protected IStream createRemoteStream(int streamId)
|
||||
protected IStream createRemoteStream(int streamId, MetaData.Request request)
|
||||
{
|
||||
// SPEC: exceeding max concurrent streams is treated as stream error.
|
||||
while (true)
|
||||
|
@ -825,7 +830,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
break;
|
||||
}
|
||||
|
||||
IStream stream = newStream(streamId, false);
|
||||
IStream stream = newStream(streamId, request, false);
|
||||
|
||||
// SPEC: duplicate stream is treated as connection error.
|
||||
if (streams.putIfAbsent(streamId, stream) == null)
|
||||
|
@ -884,6 +889,18 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
return streams.get(streamId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getLocalAddress()
|
||||
{
|
||||
return endPoint.getLocalAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress()
|
||||
{
|
||||
return endPoint.getRemoteAddress();
|
||||
}
|
||||
|
||||
@ManagedAttribute(value = "The flow control send window", readonly = true)
|
||||
public int getSendWindow()
|
||||
{
|
||||
|
@ -1556,6 +1573,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
complete();
|
||||
}
|
||||
|
||||
|
@ -1576,6 +1595,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
complete();
|
||||
}
|
||||
|
||||
|
@ -1612,6 +1633,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
complete();
|
||||
}
|
||||
|
||||
|
@ -1632,6 +1655,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(x);
|
||||
complete();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
|
@ -60,17 +61,19 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
private final long timeStamp = System.nanoTime();
|
||||
private final ISession session;
|
||||
private final int streamId;
|
||||
private final MetaData.Request request;
|
||||
private final boolean local;
|
||||
private boolean localReset;
|
||||
private Listener listener;
|
||||
private boolean remoteReset;
|
||||
private long dataLength;
|
||||
|
||||
public HTTP2Stream(Scheduler scheduler, ISession session, int streamId, boolean local)
|
||||
public HTTP2Stream(Scheduler scheduler, ISession session, int streamId, MetaData.Request request, boolean local)
|
||||
{
|
||||
super(scheduler);
|
||||
this.session = session;
|
||||
this.streamId = streamId;
|
||||
this.request = request;
|
||||
this.local = local;
|
||||
this.dataLength = Long.MIN_VALUE;
|
||||
}
|
||||
|
@ -237,6 +240,11 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
notIdle();
|
||||
switch (frame.getType())
|
||||
{
|
||||
case PREFACE:
|
||||
{
|
||||
onNewStream(callback);
|
||||
break;
|
||||
}
|
||||
case HEADERS:
|
||||
{
|
||||
onHeaders((HeadersFrame)frame, callback);
|
||||
|
@ -274,6 +282,12 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
}
|
||||
}
|
||||
|
||||
private void onNewStream(Callback callback)
|
||||
{
|
||||
notifyNewStream(this);
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
private void onHeaders(HeadersFrame frame, Callback callback)
|
||||
{
|
||||
MetaData metaData = frame.getMetaData();
|
||||
|
@ -281,7 +295,7 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
{
|
||||
HttpFields fields = metaData.getFields();
|
||||
long length = -1;
|
||||
if (fields != null)
|
||||
if (fields != null && !HttpMethod.CONNECT.is(request.getMethod()))
|
||||
length = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||
dataLength = length >= 0 ? length : Long.MIN_VALUE;
|
||||
}
|
||||
|
@ -543,6 +557,22 @@ public class HTTP2Stream extends IdleTimeout implements IStream, Callback, Dumpa
|
|||
return writing.getAndSet(null);
|
||||
}
|
||||
|
||||
private void notifyNewStream(Stream stream)
|
||||
{
|
||||
Listener listener = this.listener;
|
||||
if (listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onNewStream(stream);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Failure while notifying listener " + listener, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
Listener listener = this.listener;
|
||||
|
|
|
@ -0,0 +1,661 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.http2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadPendingException;
|
||||
import java.nio.channels.WritePendingException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.Invocable;
|
||||
|
||||
public abstract class HTTP2StreamEndPoint implements EndPoint
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(HTTP2StreamEndPoint.class);
|
||||
|
||||
private final Deque<Entry> dataQueue = new ArrayDeque<>();
|
||||
private final AtomicReference<WriteState> writeState = new AtomicReference<>(WriteState.IDLE);
|
||||
private final AtomicReference<Callback> readCallback = new AtomicReference<>();
|
||||
private final long created = System.currentTimeMillis();
|
||||
private final AtomicBoolean eof = new AtomicBoolean();
|
||||
private final AtomicBoolean closed = new AtomicBoolean();
|
||||
private final IStream stream;
|
||||
private Connection connection;
|
||||
|
||||
public HTTP2StreamEndPoint(IStream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getLocalAddress()
|
||||
{
|
||||
return stream.getSession().getLocalAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress()
|
||||
{
|
||||
return stream.getSession().getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen()
|
||||
{
|
||||
return !closed.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreatedTimeStamp()
|
||||
{
|
||||
return created;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownOutput()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case OSHUTTING:
|
||||
if (!writeState.compareAndSet(current, WriteState.OSHUT))
|
||||
break;
|
||||
stream.data(new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true), Callback.from(this::oshutSuccess, this::oshutFailure));
|
||||
return;
|
||||
case PENDING:
|
||||
if (!writeState.compareAndSet(current, WriteState.OSHUTTING))
|
||||
break;
|
||||
return;
|
||||
case OSHUT:
|
||||
case FAILED:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void oshutSuccess()
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case PENDING:
|
||||
case OSHUTTING:
|
||||
throw new IllegalStateException();
|
||||
case OSHUT:
|
||||
case FAILED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void oshutFailure(Throwable failure)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case PENDING:
|
||||
case OSHUTTING:
|
||||
throw new IllegalStateException();
|
||||
case OSHUT:
|
||||
if (!writeState.compareAndSet(current, new WriteState(WriteState.State.FAILED, failure)))
|
||||
break;
|
||||
return;
|
||||
case FAILED:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOutputShutdown()
|
||||
{
|
||||
WriteState.State state = writeState.get().state;
|
||||
return state == WriteState.State.OSHUTTING || state == WriteState.State.OSHUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInputShutdown()
|
||||
{
|
||||
return eof.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(Throwable cause)
|
||||
{
|
||||
if (closed.compareAndSet(false, true))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("closing {}, cause: {}", this, cause);
|
||||
shutdownOutput();
|
||||
stream.close();
|
||||
onClose(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fill(ByteBuffer sink) throws IOException
|
||||
{
|
||||
Entry entry;
|
||||
synchronized (this)
|
||||
{
|
||||
entry = dataQueue.poll();
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("filled {} on {}", entry, this);
|
||||
|
||||
if (entry == null)
|
||||
return 0;
|
||||
if (entry.isEOF())
|
||||
{
|
||||
entry.succeed();
|
||||
return shutdownInput();
|
||||
}
|
||||
IOException failure = entry.ioFailure();
|
||||
if (failure != null)
|
||||
{
|
||||
entry.fail(failure);
|
||||
throw failure;
|
||||
}
|
||||
|
||||
int sinkPosition = BufferUtil.flipToFill(sink);
|
||||
ByteBuffer source = entry.buffer;
|
||||
int sourceLength = source.remaining();
|
||||
int length = Math.min(sourceLength, sink.remaining());
|
||||
int sourceLimit = source.limit();
|
||||
source.limit(source.position() + length);
|
||||
sink.put(source);
|
||||
source.limit(sourceLimit);
|
||||
BufferUtil.flipToFlush(sink, sinkPosition);
|
||||
|
||||
if (source.hasRemaining())
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
dataQueue.offerFirst(entry);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.succeed();
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private int shutdownInput()
|
||||
{
|
||||
eof.set(true);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean flush(ByteBuffer... buffers) throws IOException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("flushing {} on {}", BufferUtil.toDetailString(buffers), this);
|
||||
if (buffers == null || buffers.length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
if (!writeState.compareAndSet(current, WriteState.PENDING))
|
||||
break;
|
||||
// We must copy the buffers because, differently from
|
||||
// write(), the semantic of flush() is that it does not
|
||||
// own them, but stream.data() needs to own them.
|
||||
ByteBuffer buffer = coalesce(buffers, true);
|
||||
Callback.Completable callback = new Callback.Completable(Invocable.InvocationType.NON_BLOCKING);
|
||||
stream.data(new DataFrame(stream.getId(), buffer, false), callback);
|
||||
callback.whenComplete((nothing, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
flushSuccess();
|
||||
else
|
||||
flushFailure(failure);
|
||||
});
|
||||
return callback.isDone();
|
||||
case PENDING:
|
||||
return false;
|
||||
case OSHUTTING:
|
||||
case OSHUT:
|
||||
throw new EofException("Output shutdown");
|
||||
case FAILED:
|
||||
Throwable failure = current.failure;
|
||||
if (failure instanceof IOException)
|
||||
throw (IOException)failure;
|
||||
throw new IOException(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flushSuccess()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case OSHUT:
|
||||
throw new IllegalStateException();
|
||||
case PENDING:
|
||||
if (!writeState.compareAndSet(current, WriteState.IDLE))
|
||||
break;
|
||||
return;
|
||||
case OSHUTTING:
|
||||
shutdownOutput();
|
||||
return;
|
||||
case FAILED:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flushFailure(Throwable failure)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case OSHUT:
|
||||
throw new IllegalStateException();
|
||||
case PENDING:
|
||||
if (!writeState.compareAndSet(current, new WriteState(WriteState.State.FAILED, failure)))
|
||||
break;
|
||||
return;
|
||||
case OSHUTTING:
|
||||
shutdownOutput();
|
||||
return;
|
||||
case FAILED:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTransport()
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
return stream.getIdleTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdleTimeout(long idleTimeout)
|
||||
{
|
||||
stream.setIdleTimeout(idleTimeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillInterested(Callback callback) throws ReadPendingException
|
||||
{
|
||||
if (!tryFillInterested(callback))
|
||||
throw new ReadPendingException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean tryFillInterested(Callback callback)
|
||||
{
|
||||
boolean result = readCallback.compareAndSet(null, callback);
|
||||
if (result)
|
||||
process();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFillInterested()
|
||||
{
|
||||
return readCallback.get() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("writing {} on {}", BufferUtil.toDetailString(buffers), this);
|
||||
if (buffers == null || buffers.length == 0 || remaining(buffers) == 0)
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
if (!writeState.compareAndSet(current, WriteState.PENDING))
|
||||
break;
|
||||
// TODO: we really need a Stream primitive to write multiple frames.
|
||||
ByteBuffer result = coalesce(buffers, false);
|
||||
stream.data(new DataFrame(stream.getId(), result, false), Callback.from(() -> writeSuccess(callback), x -> writeFailure(x, callback)));
|
||||
return;
|
||||
case PENDING:
|
||||
callback.failed(new WritePendingException());
|
||||
return;
|
||||
case OSHUTTING:
|
||||
case OSHUT:
|
||||
callback.failed(new EofException("Output shutdown"));
|
||||
return;
|
||||
case FAILED:
|
||||
callback.failed(current.failure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSuccess(Callback callback)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case OSHUT:
|
||||
callback.failed(new IllegalStateException());
|
||||
return;
|
||||
case PENDING:
|
||||
if (!writeState.compareAndSet(current, WriteState.IDLE))
|
||||
break;
|
||||
callback.succeeded();
|
||||
return;
|
||||
case OSHUTTING:
|
||||
callback.succeeded();
|
||||
shutdownOutput();
|
||||
return;
|
||||
case FAILED:
|
||||
callback.failed(current.failure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
WriteState current = writeState.get();
|
||||
switch (current.state)
|
||||
{
|
||||
case IDLE:
|
||||
case OSHUT:
|
||||
callback.failed(new IllegalStateException(failure));
|
||||
return;
|
||||
case PENDING:
|
||||
case OSHUTTING:
|
||||
if (!writeState.compareAndSet(current, new WriteState(WriteState.State.FAILED, failure)))
|
||||
break;
|
||||
callback.failed(failure);
|
||||
return;
|
||||
case FAILED:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long remaining(ByteBuffer... buffers)
|
||||
{
|
||||
long total = 0;
|
||||
for (ByteBuffer buffer : buffers)
|
||||
total += buffer.remaining();
|
||||
return total;
|
||||
}
|
||||
|
||||
private ByteBuffer coalesce(ByteBuffer[] buffers, boolean forceCopy)
|
||||
{
|
||||
if (buffers.length == 1 && !forceCopy)
|
||||
return buffers[0];
|
||||
long capacity = remaining(buffers);
|
||||
if (capacity > Integer.MAX_VALUE)
|
||||
throw new BufferOverflowException();
|
||||
ByteBuffer result = BufferUtil.allocateDirect((int)capacity);
|
||||
for (ByteBuffer buffer : buffers)
|
||||
BufferUtil.append(result, buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection()
|
||||
{
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConnection(Connection connection)
|
||||
{
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onOpen {}", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Throwable cause)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("onClose {}", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimizedForDirectBuffers()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgrade(Connection newConnection)
|
||||
{
|
||||
Connection oldConnection = getConnection();
|
||||
|
||||
ByteBuffer buffer = null;
|
||||
if (oldConnection instanceof Connection.UpgradeFrom)
|
||||
buffer = ((Connection.UpgradeFrom)oldConnection).onUpgradeFrom();
|
||||
|
||||
if (oldConnection != null)
|
||||
oldConnection.onClose(null);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("upgrading from {} to {} with data {} on {}", oldConnection, newConnection, BufferUtil.toDetailString(buffer), this);
|
||||
|
||||
setConnection(newConnection);
|
||||
if (newConnection instanceof Connection.UpgradeTo && buffer != null)
|
||||
((Connection.UpgradeTo)newConnection).onUpgradeTo(buffer);
|
||||
|
||||
newConnection.onOpen();
|
||||
}
|
||||
|
||||
protected void offerData(DataFrame frame, Callback callback)
|
||||
{
|
||||
ByteBuffer buffer = frame.getData();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("offering {} on {}", frame, this);
|
||||
if (frame.isEndStream())
|
||||
{
|
||||
if (buffer.hasRemaining())
|
||||
offer(buffer, Callback.from(() -> {}, callback::failed), null);
|
||||
offer(BufferUtil.EMPTY_BUFFER, callback, Entry.EOF);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (buffer.hasRemaining())
|
||||
offer(buffer, callback, null);
|
||||
else
|
||||
callback.succeeded();
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
protected void offerFailure(Throwable failure)
|
||||
{
|
||||
offer(BufferUtil.EMPTY_BUFFER, Callback.NOOP, failure);
|
||||
process();
|
||||
}
|
||||
|
||||
private void offer(ByteBuffer buffer, Callback callback, Throwable failure)
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
dataQueue.offer(new Entry(buffer, callback, failure));
|
||||
}
|
||||
}
|
||||
|
||||
protected void process()
|
||||
{
|
||||
boolean empty;
|
||||
synchronized (this)
|
||||
{
|
||||
empty = dataQueue.isEmpty();
|
||||
}
|
||||
if (!empty)
|
||||
{
|
||||
Callback callback = readCallback.getAndSet(null);
|
||||
if (callback != null)
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
// Do not call Stream.toString() because it stringifies the attachment,
|
||||
// which could be this instance, therefore causing a StackOverflowError.
|
||||
return String.format("%s@%x[%s@%x#%d][w=%s]", getClass().getSimpleName(), hashCode(),
|
||||
stream.getClass().getSimpleName(), stream.hashCode(), stream.getId(),
|
||||
writeState);
|
||||
}
|
||||
|
||||
private static class Entry
|
||||
{
|
||||
private static final Throwable EOF = new Throwable();
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
private final Callback callback;
|
||||
private final Throwable failure;
|
||||
|
||||
private Entry(ByteBuffer buffer, Callback callback, Throwable failure)
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.callback = callback;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
private boolean isEOF()
|
||||
{
|
||||
return failure == EOF;
|
||||
}
|
||||
|
||||
private IOException ioFailure()
|
||||
{
|
||||
if (failure == null || isEOF())
|
||||
return null;
|
||||
return failure instanceof IOException ? (IOException)failure : new IOException(failure);
|
||||
}
|
||||
|
||||
private void succeed()
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
private void fail(Throwable failure)
|
||||
{
|
||||
callback.failed(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[b=%s,eof=%b,f=%s]", getClass().getSimpleName(), hashCode(),
|
||||
BufferUtil.toDetailString(buffer), isEOF(), isEOF() ? null : failure);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WriteState
|
||||
{
|
||||
public static final WriteState IDLE = new WriteState(State.IDLE);
|
||||
public static final WriteState PENDING = new WriteState(State.PENDING);
|
||||
public static final WriteState OSHUTTING = new WriteState(State.OSHUTTING);
|
||||
public static final WriteState OSHUT = new WriteState(State.OSHUT);
|
||||
|
||||
private final State state;
|
||||
private final Throwable failure;
|
||||
|
||||
private WriteState(State state)
|
||||
{
|
||||
this(state, null);
|
||||
}
|
||||
|
||||
private WriteState(State state, Throwable failure)
|
||||
{
|
||||
this.state = state;
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return state.toString();
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
IDLE, PENDING, OSHUTTING, OSHUT, FAILED
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.http2.api;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -125,6 +126,18 @@ public interface Session
|
|||
*/
|
||||
public Stream getStream(int streamId);
|
||||
|
||||
/**
|
||||
* @return the local network address this session is bound to,
|
||||
* or {@code null} if this session is not bound to a network address
|
||||
*/
|
||||
public InetSocketAddress getLocalAddress();
|
||||
|
||||
/**
|
||||
* @return the remote network address this session is connected to,
|
||||
* or {@code null} if this session is not connected to a network address
|
||||
*/
|
||||
public InetSocketAddress getRemoteAddress();
|
||||
|
||||
/**
|
||||
* <p>A {@link Listener} is the passive counterpart of a {@link Session} and
|
||||
* receives events happening on a HTTP/2 connection.</p>
|
||||
|
|
|
@ -137,6 +137,16 @@ public interface Stream
|
|||
*/
|
||||
public interface Listener
|
||||
{
|
||||
/**
|
||||
* <p>Callback method invoked when a stream is created locally by
|
||||
* {@link Session#newStream(HeadersFrame, Promise, Listener)}.</p>
|
||||
*
|
||||
* @param stream the newly created stream
|
||||
*/
|
||||
public default void onNewStream(Stream stream)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Callback method invoked when a HEADERS frame representing the HTTP response has been received.</p>
|
||||
*
|
||||
|
|
|
@ -24,9 +24,9 @@ public class PushPromiseFrame extends Frame
|
|||
{
|
||||
private final int streamId;
|
||||
private final int promisedStreamId;
|
||||
private final MetaData metaData;
|
||||
private final MetaData.Request metaData;
|
||||
|
||||
public PushPromiseFrame(int streamId, int promisedStreamId, MetaData metaData)
|
||||
public PushPromiseFrame(int streamId, int promisedStreamId, MetaData.Request metaData)
|
||||
{
|
||||
super(FrameType.PUSH_PROMISE);
|
||||
this.streamId = streamId;
|
||||
|
@ -44,7 +44,7 @@ public class PushPromiseFrame extends Frame
|
|||
return promisedStreamId;
|
||||
}
|
||||
|
||||
public MetaData getMetaData()
|
||||
public MetaData.Request getMetaData()
|
||||
{
|
||||
return metaData;
|
||||
}
|
||||
|
|
|
@ -54,6 +54,6 @@ public class SettingsFrame extends Frame
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s,reply=%b:%s", super.toString(), reply, settings);
|
||||
return String.format("%s,reply=%b,params=%s", super.toString(), reply, settings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public class PushPromiseBodyParser extends BodyParser
|
|||
}
|
||||
case HEADERS:
|
||||
{
|
||||
MetaData metaData = headerBlockParser.parse(buffer, length);
|
||||
MetaData.Request metaData = (MetaData.Request)headerBlockParser.parse(buffer, length);
|
||||
if (metaData == HeaderBlockParser.SESSION_FAILURE)
|
||||
return false;
|
||||
if (metaData != null)
|
||||
|
@ -157,7 +157,7 @@ public class PushPromiseBodyParser extends BodyParser
|
|||
return false;
|
||||
}
|
||||
|
||||
private void onPushPromise(int streamId, MetaData metaData)
|
||||
private void onPushPromise(int streamId, MetaData.Request metaData)
|
||||
{
|
||||
PushPromiseFrame frame = new PushPromiseFrame(getStreamId(), streamId, metaData);
|
||||
notifyPushPromise(frame);
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
|||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
|
@ -165,10 +166,17 @@ public class HpackEncoder
|
|||
|
||||
// TODO optimise these to avoid HttpField creation
|
||||
String scheme = request.getURI().getScheme();
|
||||
encode(buffer, new HttpField(HttpHeader.C_SCHEME, scheme == null ? HttpScheme.HTTP.asString() : scheme));
|
||||
encode(buffer, new HttpField(HttpHeader.C_METHOD, request.getMethod()));
|
||||
encode(buffer, new HttpField(HttpHeader.C_AUTHORITY, request.getURI().getAuthority()));
|
||||
encode(buffer, new HttpField(HttpHeader.C_PATH, request.getURI().getPathQuery()));
|
||||
encode(buffer,new HttpField(HttpHeader.C_METHOD,request.getMethod()));
|
||||
encode(buffer,new HttpField(HttpHeader.C_AUTHORITY,request.getURI().getAuthority()));
|
||||
boolean isConnect = HttpMethod.CONNECT.is(request.getMethod());
|
||||
String protocol = request.getProtocol();
|
||||
if (!isConnect || protocol != null)
|
||||
{
|
||||
encode(buffer,new HttpField(HttpHeader.C_SCHEME,scheme == null ? HttpScheme.HTTP.asString() : scheme));
|
||||
encode(buffer,new HttpField(HttpHeader.C_PATH,request.getURI().getPathQuery()));
|
||||
if (protocol != null)
|
||||
encode(buffer,new HttpField(HttpHeader.C_PROTOCOL,protocol));
|
||||
}
|
||||
}
|
||||
else if (metadata.isResponse())
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.eclipse.jetty.http.HostPortHttpField;
|
|||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
|
@ -36,6 +37,7 @@ public class MetaDataBuilder
|
|||
private HttpScheme _scheme;
|
||||
private HostPortHttpField _authority;
|
||||
private String _path;
|
||||
private String _protocol;
|
||||
private long _contentLength = Long.MIN_VALUE;
|
||||
private HttpFields _fields = new HttpFields();
|
||||
private HpackException.StreamException _streamException;
|
||||
|
@ -140,6 +142,23 @@ public class MetaDataBuilder
|
|||
_request = true;
|
||||
break;
|
||||
|
||||
case C_PATH:
|
||||
if (checkPseudoHeader(header, _path))
|
||||
{
|
||||
if (value != null && value.length() > 0)
|
||||
_path = value;
|
||||
else
|
||||
streamException("No Path");
|
||||
}
|
||||
_request = true;
|
||||
break;
|
||||
|
||||
case C_PROTOCOL:
|
||||
if (checkPseudoHeader(header, _protocol))
|
||||
_protocol = value;
|
||||
_request = true;
|
||||
break;
|
||||
|
||||
case HOST:
|
||||
// :authority fields must come first. If we have one, ignore the host header as far as authority goes.
|
||||
if (_authority == null)
|
||||
|
@ -152,37 +171,26 @@ public class MetaDataBuilder
|
|||
_fields.add(field);
|
||||
break;
|
||||
|
||||
case C_PATH:
|
||||
if (checkPseudoHeader(header, _path))
|
||||
{
|
||||
if (value != null && value.length() > 0)
|
||||
_path = value;
|
||||
else
|
||||
streamException("No Path");
|
||||
}
|
||||
_request = true;
|
||||
break;
|
||||
|
||||
case CONTENT_LENGTH:
|
||||
_contentLength = field.getLongValue();
|
||||
_fields.add(field);
|
||||
break;
|
||||
|
||||
|
||||
case TE:
|
||||
if ("trailers".equalsIgnoreCase(value))
|
||||
_fields.add(field);
|
||||
else
|
||||
streamException("Unsupported TE value '%s'", value);
|
||||
break;
|
||||
|
||||
|
||||
case CONNECTION:
|
||||
if ("TE".equalsIgnoreCase(value))
|
||||
_fields.add(field);
|
||||
else
|
||||
streamException("Connection specific field '%s'", header);
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
default:
|
||||
if (name.charAt(0) == ':')
|
||||
streamException("Unknown pseudo header '%s'", name);
|
||||
else
|
||||
|
@ -228,7 +236,7 @@ public class MetaDataBuilder
|
|||
_streamException.addSuppressed(new Throwable());
|
||||
throw _streamException;
|
||||
}
|
||||
|
||||
|
||||
if (_request && _response)
|
||||
throw new HpackException.StreamException("Request and Response headers");
|
||||
|
||||
|
@ -239,11 +247,18 @@ public class MetaDataBuilder
|
|||
{
|
||||
if (_method == null)
|
||||
throw new HpackException.StreamException("No Method");
|
||||
if (_scheme == null)
|
||||
throw new HpackException.StreamException("No Scheme");
|
||||
if (_path == null)
|
||||
throw new HpackException.StreamException("No Path");
|
||||
return new MetaData.Request(_method, _scheme, _authority, _path, HttpVersion.HTTP_2, fields, _contentLength);
|
||||
boolean isConnect = HttpMethod.CONNECT.is(_method);
|
||||
if (!isConnect || _protocol != null)
|
||||
{
|
||||
if (_scheme == null)
|
||||
throw new HpackException.StreamException("No Scheme");
|
||||
if (_path == null)
|
||||
throw new HpackException.StreamException("No Path");
|
||||
}
|
||||
if (isConnect)
|
||||
return new MetaData.ConnectRequest(_scheme, _authority, _path, fields, _protocol);
|
||||
else
|
||||
return new MetaData.Request(_method, _scheme, _authority, _path, HttpVersion.HTTP_2, fields, _contentLength);
|
||||
}
|
||||
if (_response)
|
||||
{
|
||||
|
@ -251,7 +266,7 @@ public class MetaDataBuilder
|
|||
throw new HpackException.StreamException("No Status");
|
||||
return new MetaData.Response(HttpVersion.HTTP_2, _status, fields, _contentLength);
|
||||
}
|
||||
|
||||
|
||||
return new MetaData(HttpVersion.HTTP_2, fields, _contentLength);
|
||||
}
|
||||
finally
|
||||
|
@ -264,6 +279,7 @@ public class MetaDataBuilder
|
|||
_scheme = null;
|
||||
_authority = null;
|
||||
_path = null;
|
||||
_protocol = null;
|
||||
_size = 0;
|
||||
_contentLength = Long.MIN_VALUE;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.http2.client.http;
|
||||
|
||||
import org.eclipse.jetty.http2.HTTP2Channel;
|
||||
import org.eclipse.jetty.http2.HTTP2StreamEndPoint;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class ClientHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HTTP2Channel.Client
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ClientHTTP2StreamEndPoint.class);
|
||||
|
||||
public ClientHTTP2StreamEndPoint(IStream stream)
|
||||
{
|
||||
super(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(DataFrame frame, Callback callback)
|
||||
{
|
||||
offerData(frame, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTimeout(Throwable failure)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("idle timeout on {}: {}", this, failure);
|
||||
Connection connection = getConnection();
|
||||
if (connection != null)
|
||||
return connection.onIdleExpired();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
callback.failed(failure);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package org.eclipse.jetty.http2.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jetty.client.HttpChannel;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
|
@ -25,14 +27,19 @@ import org.eclipse.jetty.client.HttpReceiver;
|
|||
import org.eclipse.jetty.client.HttpSender;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
import org.eclipse.jetty.http2.HTTP2Channel;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
|
||||
public class HttpChannelOverHTTP2 extends HttpChannel
|
||||
{
|
||||
private final Stream.Listener listener = new Listener();
|
||||
private final HttpConnectionOverHTTP2 connection;
|
||||
private final Session session;
|
||||
private final HttpSenderOverHTTP2 sender;
|
||||
|
@ -60,7 +67,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
|
||||
public Stream.Listener getStreamListener()
|
||||
{
|
||||
return receiver;
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,6 +90,8 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
public void setStream(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
if (stream != null)
|
||||
((IStream)stream).setAttachment(receiver);
|
||||
}
|
||||
|
||||
public boolean isFailed()
|
||||
|
@ -156,4 +165,60 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
}
|
||||
|
||||
private class Listener implements Stream.Listener
|
||||
{
|
||||
@Override
|
||||
public void onNewStream(Stream stream)
|
||||
{
|
||||
setStream(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
receiver.onHeaders(stream, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
||||
{
|
||||
return receiver.onPush(stream, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment();
|
||||
channel.onData(frame, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(Stream stream, ResetFrame frame)
|
||||
{
|
||||
// TODO: needs to call HTTP2Channel?
|
||||
receiver.onReset(stream, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onIdleTimeout(Stream stream, Throwable x)
|
||||
{
|
||||
HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment();
|
||||
return channel.onTimeout(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Stream stream, int error, String reason, Callback callback)
|
||||
{
|
||||
HTTP2Channel.Client channel = (HTTP2Channel.Client)((IStream)stream).getAttachment();
|
||||
channel.onFailure(new IOException(String.format("Failure %s/%s", ErrorCode.toString(error, null), reason)), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(Stream stream)
|
||||
{
|
||||
// TODO: needs to call HTTP2Channel?
|
||||
receiver.onClosed(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,17 @@ package org.eclipse.jetty.http2.client.http;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
|
||||
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.MultiplexConnectionPool;
|
||||
import org.eclipse.jetty.client.MultiplexHttpDestination;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.ProxyConfiguration;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
|
@ -104,6 +107,13 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
removeBean(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
{
|
||||
String protocol = HttpScheme.HTTPS.is(origin.getScheme()) ? "h2" : "h2c";
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(List.of(protocol), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
{
|
||||
|
|
|
@ -35,21 +35,24 @@ 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.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
import org.eclipse.jetty.http2.HTTP2Channel;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
import org.eclipse.jetty.util.Retainable;
|
||||
|
||||
public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listener
|
||||
public class HttpReceiverOverHTTP2 extends HttpReceiver implements HTTP2Channel.Client
|
||||
{
|
||||
private final ContentNotifier contentNotifier = new ContentNotifier();
|
||||
|
||||
|
@ -71,8 +74,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
contentNotifier.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
|
@ -94,6 +96,19 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
return;
|
||||
}
|
||||
|
||||
HttpRequest httpRequest = exchange.getRequest();
|
||||
if (HttpMethod.CONNECT.is(httpRequest.getMethod()) && httpResponse.getStatus() == HttpStatus.OK_200)
|
||||
{
|
||||
ClientHTTP2StreamEndPoint endPoint = new ClientHTTP2StreamEndPoint((IStream)stream);
|
||||
long idleTimeout = httpRequest.getIdleTimeout();
|
||||
if (idleTimeout > 0)
|
||||
endPoint.setIdleTimeout(idleTimeout);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Successful HTTP2 tunnel on {} via {}", stream, endPoint);
|
||||
((IStream)stream).setAttachment(endPoint);
|
||||
httpRequest.getConversation().setAttribute(EndPoint.class.getName(), endPoint);
|
||||
}
|
||||
|
||||
if (responseHeaders(exchange))
|
||||
{
|
||||
int status = response.getStatus();
|
||||
|
@ -111,15 +126,14 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
||||
Stream.Listener onPush(Stream stream, PushPromiseFrame frame)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return null;
|
||||
|
||||
HttpRequest request = exchange.getRequest();
|
||||
MetaData.Request metaData = (MetaData.Request)frame.getMetaData();
|
||||
MetaData.Request metaData = frame.getMetaData();
|
||||
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
|
||||
// TODO: copy PUSH_PROMISE headers into pushRequest.
|
||||
|
||||
|
@ -146,7 +160,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
public void onData(DataFrame frame, Callback callback)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
|
@ -159,8 +173,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(Stream stream, ResetFrame frame)
|
||||
void onReset(Stream stream, ResetFrame frame)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
|
@ -170,23 +183,22 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onIdleTimeout(Stream stream, Throwable x)
|
||||
public boolean onTimeout(Throwable failure)
|
||||
{
|
||||
HttpExchange exchange = getHttpExchange();
|
||||
if (exchange == null)
|
||||
return false;
|
||||
return !exchange.abort(x);
|
||||
return !exchange.abort(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Stream stream, int error, String reason, Callback callback)
|
||||
public void onFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
responseFailure(new IOException(String.format("%s/%s", ErrorCode.toString(error, null), reason)));
|
||||
responseFailure(failure);
|
||||
callback.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(Stream stream)
|
||||
void onClosed(Stream stream)
|
||||
{
|
||||
getHttpChannel().onStreamClosed((IStream)stream);
|
||||
}
|
||||
|
|
|
@ -19,17 +19,19 @@
|
|||
package org.eclipse.jetty.http2.client.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jetty.client.HttpContent;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.HttpSender;
|
||||
import org.eclipse.jetty.http.HostPortHttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
|
@ -53,23 +55,35 @@ public class HttpSenderOverHTTP2 extends HttpSender
|
|||
protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
|
||||
{
|
||||
HttpRequest request = exchange.getRequest();
|
||||
String path = relativize(request.getPath());
|
||||
HttpURI uri = HttpURI.createHttpURI(request.getScheme(), request.getHost(), request.getPort(), path, null, request.getQuery(), null);
|
||||
MetaData.Request metaData = new MetaData.Request(request.getMethod(), uri, HttpVersion.HTTP_2, request.getHeaders());
|
||||
boolean isTunnel = HttpMethod.CONNECT.is(request.getMethod());
|
||||
MetaData.Request metaData;
|
||||
if (isTunnel)
|
||||
{
|
||||
metaData = new MetaData.Request(request.getMethod(), null, new HostPortHttpField(request.getPath()), null, HttpVersion.HTTP_2, request.getHeaders());
|
||||
}
|
||||
else
|
||||
{
|
||||
String path = relativize(request.getPath());
|
||||
HttpURI uri = HttpURI.createHttpURI(request.getScheme(), request.getHost(), request.getPort(), path, null, request.getQuery(), null);
|
||||
metaData = new MetaData.Request(request.getMethod(), uri, HttpVersion.HTTP_2, request.getHeaders());
|
||||
}
|
||||
Supplier<HttpFields> trailerSupplier = request.getTrailers();
|
||||
metaData.setTrailerSupplier(trailerSupplier);
|
||||
|
||||
HeadersFrame headersFrame;
|
||||
Promise<Stream> promise;
|
||||
if (content.hasContent())
|
||||
if (isTunnel)
|
||||
{
|
||||
headersFrame = new HeadersFrame(metaData, null, false);
|
||||
promise = new HeadersPromise(request, callback)
|
||||
promise = new HeadersPromise(request, callback, stream -> callback.succeeded());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (content.hasContent())
|
||||
{
|
||||
@Override
|
||||
public void succeeded(Stream stream)
|
||||
headersFrame = new HeadersFrame(metaData, null, false);
|
||||
promise = new HeadersPromise(request, callback, stream ->
|
||||
{
|
||||
super.succeeded(stream);
|
||||
if (expects100Continue(request))
|
||||
{
|
||||
// Don't send the content yet.
|
||||
|
@ -84,26 +98,21 @@ public class HttpSenderOverHTTP2 extends HttpSender
|
|||
else
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpFields trailers = trailerSupplier == null ? null : trailerSupplier.get();
|
||||
boolean endStream = trailers == null || trailers.size() == 0;
|
||||
headersFrame = new HeadersFrame(metaData, null, endStream);
|
||||
promise = new HeadersPromise(request, callback)
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@Override
|
||||
public void succeeded(Stream stream)
|
||||
HttpFields trailers = trailerSupplier == null ? null : trailerSupplier.get();
|
||||
boolean endStream = trailers == null || trailers.size() <= 0;
|
||||
headersFrame = new HeadersFrame(metaData, null, endStream);
|
||||
promise = new HeadersPromise(request, callback, stream ->
|
||||
{
|
||||
super.succeeded(stream);
|
||||
if (endStream)
|
||||
callback.succeeded();
|
||||
else
|
||||
sendTrailers(stream, trailers, callback);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
// TODO optimize the send of HEADERS and DATA frames.
|
||||
HttpChannelOverHTTP2 channel = getHttpChannel();
|
||||
|
@ -168,26 +177,26 @@ public class HttpSenderOverHTTP2 extends HttpSender
|
|||
stream.headers(trailersFrame, callback);
|
||||
}
|
||||
|
||||
private class HeadersPromise implements Promise<Stream>
|
||||
private static class HeadersPromise implements Promise<Stream>
|
||||
{
|
||||
private final HttpRequest request;
|
||||
private final Callback callback;
|
||||
private final Consumer<Stream> succeed;
|
||||
|
||||
private HeadersPromise(HttpRequest request, Callback callback)
|
||||
private HeadersPromise(HttpRequest request, Callback callback, Consumer<Stream> succeed)
|
||||
{
|
||||
this.request = request;
|
||||
this.callback = callback;
|
||||
this.succeed = succeed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(Stream stream)
|
||||
{
|
||||
HttpChannelOverHTTP2 channel = getHttpChannel();
|
||||
channel.setStream(stream);
|
||||
((IStream)stream).setAttachment(channel);
|
||||
long idleTimeout = request.getIdleTimeout();
|
||||
if (idleTimeout >= 0)
|
||||
stream.setIdleTimeout(idleTimeout);
|
||||
succeed.accept(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,6 +43,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpProxy;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
|
@ -341,7 +342,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
});
|
||||
|
||||
int proxyPort = connector.getLocalPort();
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new HttpDestination.Protocol(List.of("h2c"), false)));
|
||||
|
||||
int serverPort = proxyPort + 1; // Any port will do, just not the same as the proxy.
|
||||
ContentResponse response = client.newRequest("localhost", serverPort)
|
||||
|
|
|
@ -159,7 +159,6 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
}
|
||||
});
|
||||
|
||||
String scheme = "http";
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
|
@ -212,15 +211,15 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
|
||||
// This request will be queued and establish the connection,
|
||||
// which will trigger the send of the second request.
|
||||
ContentResponse response1 = client.newRequest(host, port)
|
||||
.path("/1")
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
var request = client.newRequest(host, port)
|
||||
.path("/1")
|
||||
.timeout(5, TimeUnit.SECONDS);
|
||||
ContentResponse response = request.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response1.getStatus());
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS), failures.toString());
|
||||
assertEquals(2, connections.get());
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
|
||||
assertEquals(2, connectionPool.getConnectionCount());
|
||||
}
|
||||
|
@ -253,16 +252,15 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
// Send the request in excess.
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
String path = "/excess";
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.path(path)
|
||||
.send(result ->
|
||||
{
|
||||
if (result.getResponse().getStatus() == HttpStatus.OK_200)
|
||||
latch.countDown();
|
||||
});
|
||||
var request = client.newRequest("localhost", connector.getLocalPort()).path(path);
|
||||
request.send(result ->
|
||||
{
|
||||
if (result.getResponse().getStatus() == HttpStatus.OK_200)
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
// The last exchange should remain in the queue.
|
||||
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
assertEquals(1, destination.getHttpExchanges().size());
|
||||
assertEquals(path, destination.getHttpExchanges().peek().getRequest().getPath());
|
||||
|
||||
|
|
|
@ -234,9 +234,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
|||
// the typical case is that the connection will be busier and the
|
||||
// stream idle timeout will expire earlier than the connection's.
|
||||
long streamIdleTimeout = getStreamIdleTimeout();
|
||||
if (streamIdleTimeout <= 0)
|
||||
streamIdleTimeout = endPoint.getIdleTimeout();
|
||||
session.setStreamIdleTimeout(streamIdleTimeout);
|
||||
if (streamIdleTimeout > 0)
|
||||
session.setStreamIdleTimeout(streamIdleTimeout);
|
||||
session.setInitialSessionRecvWindow(getInitialSessionRecvWindow());
|
||||
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
|
||||
|
||||
|
|
|
@ -35,9 +35,11 @@ import org.eclipse.jetty.http.BadMessageException;
|
|||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.MetaData.Request;
|
||||
import org.eclipse.jetty.http2.ErrorCode;
|
||||
import org.eclipse.jetty.http2.HTTP2Channel;
|
||||
import org.eclipse.jetty.http2.HTTP2Connection;
|
||||
import org.eclipse.jetty.http2.ISession;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
|
@ -86,7 +88,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final Queue<HttpChannelOverHTTP2> channels = new ArrayDeque<>();
|
||||
private final List<Frame> upgradeFrames = new ArrayList<>();
|
||||
private final AtomicLong totalRequests = new AtomicLong();
|
||||
|
@ -176,10 +178,10 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing {} on {}", frame, stream);
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
|
||||
if (channel != null)
|
||||
{
|
||||
Runnable task = channel.onRequestContent(frame, callback);
|
||||
Runnable task = channel.onData(frame, callback);
|
||||
if (task != null)
|
||||
offerTask(task, false);
|
||||
}
|
||||
|
@ -193,10 +195,10 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing trailers {} on {}", frame, stream);
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
|
||||
if (channel != null)
|
||||
{
|
||||
Runnable task = channel.onRequestTrailers(frame);
|
||||
Runnable task = channel.onTrailer(frame);
|
||||
if (task != null)
|
||||
offerTask(task, false);
|
||||
}
|
||||
|
@ -204,8 +206,8 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
|
||||
public boolean onStreamTimeout(IStream stream, Throwable failure)
|
||||
{
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
boolean result = channel != null && channel.onStreamTimeout(failure, task -> offerTask(task, true));
|
||||
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
|
||||
boolean result = channel != null && channel.onTimeout(failure, task -> offerTask(task, true));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", stream, failure);
|
||||
return result;
|
||||
|
@ -215,7 +217,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing failure on {}: {}", stream, failure);
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
HTTP2Channel.Server channel = (HTTP2Channel.Server)stream.getAttachment();
|
||||
if (channel != null)
|
||||
{
|
||||
Runnable task = channel.onFailure(failure, callback);
|
||||
|
@ -233,11 +235,11 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
ISession session = getSession();
|
||||
// Compute whether all requests are idle.
|
||||
boolean result = session.getStreams().stream()
|
||||
.map(stream -> (IStream)stream)
|
||||
.map(stream -> (HttpChannelOverHTTP2)stream.getAttachment())
|
||||
.filter(Objects::nonNull)
|
||||
.map(HttpChannelOverHTTP2::isRequestIdle)
|
||||
.reduce(true, Boolean::logicalAnd);
|
||||
.map(stream -> (IStream)stream)
|
||||
.map(stream -> (HTTP2Channel.Server)stream.getAttachment())
|
||||
.filter(Objects::nonNull)
|
||||
.map(HTTP2Channel.Server::isIdle)
|
||||
.reduce(true, Boolean::logicalAnd);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", session, failure);
|
||||
return result;
|
||||
|
@ -374,15 +376,27 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
return super.onRequest(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkAndPrepareUpgrade()
|
||||
{
|
||||
if (isTunnel())
|
||||
getHttpTransport().prepareUpgrade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted()
|
||||
{
|
||||
totalResponses.incrementAndGet();
|
||||
super.onCompleted();
|
||||
if (!getStream().isReset())
|
||||
totalResponses.incrementAndGet();
|
||||
if (!getStream().isReset() && !isTunnel())
|
||||
recycle();
|
||||
}
|
||||
|
||||
private boolean isTunnel()
|
||||
{
|
||||
return HttpMethod.CONNECT.is(getRequest().getMethod()) && getResponse().getStatus() == HttpStatus.OK_200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle()
|
||||
{
|
||||
|
|
|
@ -104,7 +104,7 @@ public class HTTP2ServerSession extends HTTP2Session implements ServerParser.Lis
|
|||
}
|
||||
else
|
||||
{
|
||||
stream = createRemoteStream(streamId);
|
||||
stream = createRemoteStream(streamId, (MetaData.Request)metaData);
|
||||
if (stream != null)
|
||||
{
|
||||
onStreamOpened(stream);
|
||||
|
|
|
@ -29,9 +29,11 @@ import org.eclipse.jetty.http.HttpFields;
|
|||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.http2.HTTP2Channel;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
|
@ -47,7 +49,7 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, WriteFlusher.Listener
|
||||
public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, WriteFlusher.Listener, HTTP2Channel.Server
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpChannelOverHTTP2.class);
|
||||
private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION);
|
||||
|
@ -139,16 +141,16 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
}
|
||||
|
||||
_delayedUntilContent = getHttpConfiguration().isDelayDispatchUntilContent() &&
|
||||
!endStream && !_expect100Continue;
|
||||
!endStream && !_expect100Continue && !HttpMethod.CONNECT.is(request.getMethod());
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
Stream stream = getStream();
|
||||
LOG.debug("HTTP2 Request #{}/{}, delayed={}:{}{} {} {}{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
_delayedUntilContent, System.lineSeparator(),
|
||||
request.getMethod(), request.getURI(), request.getHttpVersion(),
|
||||
System.lineSeparator(), fields);
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
_delayedUntilContent, System.lineSeparator(),
|
||||
request.getMethod(), request.getURI(), request.getHttpVersion(),
|
||||
System.lineSeparator(), fields);
|
||||
}
|
||||
|
||||
return _delayedUntilContent ? null : this;
|
||||
|
@ -178,9 +180,9 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
{
|
||||
Stream stream = getStream();
|
||||
LOG.debug("HTTP2 PUSH Request #{}/{}:{}{} {} {}{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(),
|
||||
request.getMethod(), request.getURI(), request.getHttpVersion(),
|
||||
System.lineSeparator(), request.getFields());
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(),
|
||||
request.getMethod(), request.getURI(), request.getHttpVersion(),
|
||||
System.lineSeparator(), request.getFields());
|
||||
}
|
||||
|
||||
return this;
|
||||
|
@ -220,11 +222,17 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
{
|
||||
Stream stream = getStream();
|
||||
LOG.debug("HTTP2 Commit Response #{}/{}:{}{} {} {}{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getHttpVersion(), info.getStatus(), info.getReason(),
|
||||
System.lineSeparator(), info.getFields());
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), System.lineSeparator(), info.getHttpVersion(), info.getStatus(), info.getReason(),
|
||||
System.lineSeparator(), info.getFields());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable onData(DataFrame frame, Callback callback)
|
||||
{
|
||||
return onRequestContent(frame, callback);
|
||||
}
|
||||
|
||||
public Runnable onRequestContent(DataFrame frame, final Callback callback)
|
||||
{
|
||||
Stream stream = getStream();
|
||||
|
@ -272,11 +280,11 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("HTTP2 Request #{}/{}: {} bytes of {} content, handle: {}",
|
||||
stream.getId(),
|
||||
Integer.toHexString(stream.getSession().hashCode()),
|
||||
length,
|
||||
endStream ? "last" : "some",
|
||||
handle);
|
||||
stream.getId(),
|
||||
Integer.toHexString(stream.getSession().hashCode()),
|
||||
length,
|
||||
endStream ? "last" : "some",
|
||||
handle);
|
||||
}
|
||||
|
||||
boolean wasDelayed = _delayedUntilContent;
|
||||
|
@ -284,7 +292,8 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
return handle || wasDelayed ? this : null;
|
||||
}
|
||||
|
||||
public Runnable onRequestTrailers(HeadersFrame frame)
|
||||
@Override
|
||||
public Runnable onTrailer(HeadersFrame frame)
|
||||
{
|
||||
HttpFields trailers = frame.getMetaData().getFields();
|
||||
if (trailers.size() > 0)
|
||||
|
@ -294,8 +303,8 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
{
|
||||
Stream stream = getStream();
|
||||
LOG.debug("HTTP2 Request #{}/{}, trailers:{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
System.lineSeparator(), trailers);
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
System.lineSeparator(), trailers);
|
||||
}
|
||||
|
||||
boolean handle = onRequestComplete();
|
||||
|
@ -305,17 +314,19 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
return handle || wasDelayed ? this : null;
|
||||
}
|
||||
|
||||
public boolean isRequestIdle()
|
||||
@Override
|
||||
public boolean isIdle()
|
||||
{
|
||||
return getState().isIdle();
|
||||
}
|
||||
|
||||
public boolean onStreamTimeout(Throwable failure, Consumer<Runnable> consumer)
|
||||
@Override
|
||||
public boolean onTimeout(Throwable failure, Consumer<Runnable> consumer)
|
||||
{
|
||||
final boolean delayed = _delayedUntilContent;
|
||||
_delayedUntilContent = false;
|
||||
|
||||
boolean result = isRequestIdle();
|
||||
boolean result = isIdle();
|
||||
if (result)
|
||||
consumeInput();
|
||||
|
||||
|
@ -329,6 +340,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable onFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
getHttpTransport().onStreamFailure(failure);
|
||||
|
@ -380,6 +392,18 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, Writ
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTunnellingSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EndPoint getTunnellingEndPoint()
|
||||
{
|
||||
return new ServerHTTP2StreamEndPoint(getStream());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
|
@ -33,8 +34,11 @@ import org.eclipse.jetty.http2.frames.DataFrame;
|
|||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.HttpTransport;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
@ -50,7 +54,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
private final Connector connector;
|
||||
private final HTTP2ServerConnection connection;
|
||||
private IStream stream;
|
||||
private MetaData metaData;
|
||||
private MetaData.Response metaData;
|
||||
|
||||
public HttpTransportOverHTTP2(Connector connector, HTTP2ServerConnection connection)
|
||||
{
|
||||
|
@ -77,13 +81,14 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
}
|
||||
|
||||
@Override
|
||||
public void send(MetaData.Response info, boolean isHeadRequest, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod());
|
||||
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
|
||||
if (info != null)
|
||||
if (response != null)
|
||||
{
|
||||
metaData = info;
|
||||
int status = info.getStatus();
|
||||
metaData = response;
|
||||
int status = response.getStatus();
|
||||
boolean interimResponse = status == HttpStatus.CONTINUE_100 || status == HttpStatus.PROCESSING_102;
|
||||
if (interimResponse)
|
||||
{
|
||||
|
@ -95,7 +100,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
else
|
||||
{
|
||||
if (transportCallback.start(callback, false))
|
||||
sendHeadersFrame(info, false, transportCallback);
|
||||
sendHeadersFrame(response, false, transportCallback);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -131,28 +136,36 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
}
|
||||
};
|
||||
if (transportCallback.start(commitCallback, true))
|
||||
sendHeadersFrame(info, false, transportCallback);
|
||||
sendHeadersFrame(response, false, transportCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastContent)
|
||||
{
|
||||
HttpFields trailers = retrieveTrailers();
|
||||
if (trailers != null)
|
||||
if (isTunnel(request, response))
|
||||
{
|
||||
if (transportCallback.start(new SendTrailers(callback, trailers), true))
|
||||
sendHeadersFrame(info, false, transportCallback);
|
||||
if (transportCallback.start(callback, true))
|
||||
sendHeadersFrame(response, false, transportCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transportCallback.start(callback, true))
|
||||
sendHeadersFrame(info, true, transportCallback);
|
||||
HttpFields trailers = retrieveTrailers();
|
||||
if (trailers != null)
|
||||
{
|
||||
if (transportCallback.start(new SendTrailers(callback, trailers), true))
|
||||
sendHeadersFrame(response, false, transportCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transportCallback.start(callback, true))
|
||||
sendHeadersFrame(response, true, transportCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (transportCallback.start(callback, true))
|
||||
sendHeadersFrame(info, false, transportCallback);
|
||||
sendHeadersFrame(response, false, transportCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +177,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
}
|
||||
else
|
||||
{
|
||||
if (hasContent || lastContent)
|
||||
if (hasContent || (lastContent && !isTunnel(request, response)))
|
||||
{
|
||||
if (lastContent)
|
||||
{
|
||||
|
@ -212,6 +225,11 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
return trailers.size() == 0 ? null : trailers;
|
||||
}
|
||||
|
||||
private boolean isTunnel(MetaData.Request request, MetaData.Response response)
|
||||
{
|
||||
return HttpMethod.CONNECT.is(request.getMethod()) && response.getStatus() == HttpStatus.OK_200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPushSupported()
|
||||
{
|
||||
|
@ -231,7 +249,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("HTTP/2 Push {}", request);
|
||||
|
||||
stream.push(new PushPromiseFrame(stream.getId(), 0, request), new Promise<Stream>()
|
||||
stream.push(new PushPromiseFrame(stream.getId(), 0, request), new Promise<>()
|
||||
{
|
||||
@Override
|
||||
public void succeeded(Stream pushStream)
|
||||
|
@ -296,23 +314,50 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
return transportCallback.onIdleTimeout(failure);
|
||||
}
|
||||
|
||||
void prepareUpgrade()
|
||||
{
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
Request request = channel.getRequest();
|
||||
Connection connection = (Connection)request.getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
|
||||
EndPoint endPoint = connection.getEndPoint();
|
||||
endPoint.upgrade(connection);
|
||||
stream.setAttachment(endPoint);
|
||||
if (request.getHttpInput().hasContent())
|
||||
channel.sendErrorOrAbort("Unexpected content in CONNECT request");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted()
|
||||
{
|
||||
// If the stream is not closed, it is still reading the request content.
|
||||
// Send a reset to the other end so that it stops sending data.
|
||||
if (!stream.isClosed())
|
||||
Object attachment = stream.getAttachment();
|
||||
if (attachment instanceof HttpChannelOverHTTP2)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("HTTP2 Response #{}: unconsumed request content, resetting stream", stream.getId());
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
||||
}
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)attachment;
|
||||
if (channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||
{
|
||||
Connection connection = (Connection)channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
|
||||
EndPoint endPoint = connection.getEndPoint();
|
||||
// TODO: check that endPoint implements HTTP2Channel.
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Tunnelling DATA frames through {}", endPoint);
|
||||
endPoint.upgrade(connection);
|
||||
stream.setAttachment(endPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume the existing queued data frames to
|
||||
// avoid stalling the session flow control.
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
|
||||
if (channel != null)
|
||||
// If the stream is not closed, it is still reading the request content.
|
||||
// Send a reset to the other end so that it stops sending data.
|
||||
if (!stream.isClosed())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("HTTP2 Response #{}: unconsumed request content, resetting stream", stream.getId());
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
||||
}
|
||||
|
||||
// Consume the existing queued data frames to
|
||||
// avoid stalling the session flow control.
|
||||
channel.consumeInput();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.http2.server;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jetty.http2.HTTP2Channel;
|
||||
import org.eclipse.jetty.http2.HTTP2StreamEndPoint;
|
||||
import org.eclipse.jetty.http2.IStream;
|
||||
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class ServerHTTP2StreamEndPoint extends HTTP2StreamEndPoint implements HTTP2Channel.Server
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ServerHTTP2StreamEndPoint.class);
|
||||
|
||||
public ServerHTTP2StreamEndPoint(IStream stream)
|
||||
{
|
||||
super(stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable onData(DataFrame frame, Callback callback)
|
||||
{
|
||||
offerData(frame, callback);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable onTrailer(HeadersFrame frame)
|
||||
{
|
||||
// We are tunnelling, so there are no trailers.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTimeout(Throwable failure, Consumer<Runnable> consumer)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("idle timeout on {}: {}", this, failure);
|
||||
offerFailure(failure);
|
||||
boolean result = true;
|
||||
Connection connection = getConnection();
|
||||
if (connection != null)
|
||||
result = connection.onIdleExpired();
|
||||
consumer.accept(() -> close(failure));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable onFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("failure on {}: {}", this, failure);
|
||||
offerFailure(failure);
|
||||
close(failure);
|
||||
return callback::succeeded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdle()
|
||||
{
|
||||
// We are tunnelling, so we are never idle.
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -81,7 +81,13 @@ public interface ClientConnectionFactory
|
|||
*/
|
||||
public boolean matches(List<String> candidates)
|
||||
{
|
||||
return protocols.stream().anyMatch(candidates::contains);
|
||||
return protocols.stream().anyMatch(p -> candidates.stream().anyMatch(c -> c.equalsIgnoreCase(p)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,11 +30,6 @@
|
|||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.weld.servlet</groupId>
|
||||
<artifactId>weld-servlet</artifactId>
|
||||
<version>@weld.version@</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-perf-helper</artifactId>
|
||||
|
|
|
@ -18,10 +18,6 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.weld.servlet</groupId>
|
||||
<artifactId>weld-servlet</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-servlet-api</artifactId>
|
||||
|
|
|
@ -48,6 +48,11 @@
|
|||
<artifactId>jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-alpn-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
|
|
@ -21,6 +21,7 @@ module org.eclipse.jetty.proxy
|
|||
exports org.eclipse.jetty.proxy;
|
||||
|
||||
requires jetty.servlet.api;
|
||||
requires org.eclipse.jetty.alpn.client;
|
||||
requires org.eclipse.jetty.client;
|
||||
requires org.eclipse.jetty.http;
|
||||
requires org.eclipse.jetty.io;
|
||||
|
|
|
@ -42,13 +42,14 @@ import org.eclipse.jetty.client.HttpClient;
|
|||
import org.eclipse.jetty.client.ProtocolHandlers;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.HttpCookieStore;
|
||||
import org.eclipse.jetty.util.ProcessorUtils;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -350,11 +351,23 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
*/
|
||||
protected HttpClient newHttpClient()
|
||||
{
|
||||
int selectors = Math.max(1, ProcessorUtils.availableProcessors() / 2);
|
||||
int selectors = 1;
|
||||
String value = getServletConfig().getInitParameter("selectors");
|
||||
if (value != null)
|
||||
selectors = Integer.parseInt(value);
|
||||
return new HttpClient(new HttpClientTransportOverHTTP(selectors));
|
||||
ClientConnector clientConnector = newClientConnector();
|
||||
clientConnector.setSelectors(selectors);
|
||||
return newHttpClient(clientConnector);
|
||||
}
|
||||
|
||||
protected HttpClient newHttpClient(ClientConnector clientConnector)
|
||||
{
|
||||
return new HttpClient(new HttpClientTransportDynamic(clientConnector));
|
||||
}
|
||||
|
||||
protected ClientConnector newClientConnector()
|
||||
{
|
||||
return new ClientConnector();
|
||||
}
|
||||
|
||||
protected HttpClient getHttpClient()
|
||||
|
@ -411,8 +424,14 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
{
|
||||
if (!validateDestination(clientRequest.getServerName(), clientRequest.getServerPort()))
|
||||
return null;
|
||||
|
||||
// If the proxy is secure, we will likely get a proxied URI
|
||||
// with the "https" scheme, but the upstream server needs
|
||||
// to be called with the "http" scheme (the ConnectHandler
|
||||
// is used to call upstream servers with the "https" scheme).
|
||||
StringBuffer target = clientRequest.getRequestURL();
|
||||
// Change "https" to "http".
|
||||
if (HttpScheme.HTTPS.is(target.substring(0, 5)))
|
||||
target.replace(4, 5, "");
|
||||
String query = clientRequest.getQueryString();
|
||||
if (query != null)
|
||||
target.append("?").append(query);
|
||||
|
|
|
@ -38,6 +38,8 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
|
@ -46,7 +48,7 @@ import org.eclipse.jetty.io.MappedByteBufferPool;
|
|||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConnection;
|
||||
import org.eclipse.jetty.server.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpTransport;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||
|
@ -192,19 +194,24 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
if (HttpMethod.CONNECT.is(request.getMethod()))
|
||||
String tunnelProtocol = jettyRequest.getMetaData().getProtocol();
|
||||
if (HttpMethod.CONNECT.is(request.getMethod()) && tunnelProtocol == null)
|
||||
{
|
||||
String serverAddress = request.getRequestURI();
|
||||
String serverAddress = target;
|
||||
if (HttpVersion.HTTP_2.is(request.getProtocol()))
|
||||
{
|
||||
HttpURI httpURI = jettyRequest.getHttpURI();
|
||||
serverAddress = httpURI.getHost() + ":" + httpURI.getPort();
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("CONNECT request for {}", serverAddress);
|
||||
|
||||
handleConnect(baseRequest, request, response, serverAddress);
|
||||
handleConnect(jettyRequest, request, response, serverAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
super.handle(target, baseRequest, request, response);
|
||||
super.handle(target, jettyRequest, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,12 +251,11 @@ public class ConnectHandler extends HandlerWrapper
|
|||
return;
|
||||
}
|
||||
|
||||
HttpTransport transport = baseRequest.getHttpChannel().getHttpTransport();
|
||||
// TODO Handle CONNECT over HTTP2!
|
||||
if (!(transport instanceof HttpConnection))
|
||||
HttpChannel httpChannel = baseRequest.getHttpChannel();
|
||||
if (!httpChannel.isTunnellingSupported())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("CONNECT not supported for {}", transport);
|
||||
LOG.debug("CONNECT not supported for {}", httpChannel);
|
||||
sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
@ -260,12 +266,12 @@ public class ConnectHandler extends HandlerWrapper
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Connecting to {}:{}", host, port);
|
||||
|
||||
connectToServer(request, host, port, new Promise<SocketChannel>()
|
||||
connectToServer(request, host, port, new Promise<>()
|
||||
{
|
||||
@Override
|
||||
public void succeeded(SocketChannel channel)
|
||||
{
|
||||
ConnectContext connectContext = new ConnectContext(request, response, asyncContext, (HttpConnection)transport);
|
||||
ConnectContext connectContext = new ConnectContext(request, response, asyncContext, httpChannel.getTunnellingEndPoint());
|
||||
if (channel.isConnected())
|
||||
selector.accept(channel, connectContext);
|
||||
else
|
||||
|
@ -335,8 +341,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
HttpServletRequest request = connectContext.getRequest();
|
||||
prepareContext(request, context);
|
||||
|
||||
HttpConnection httpConnection = connectContext.getHttpConnection();
|
||||
EndPoint downstreamEndPoint = httpConnection.getEndPoint();
|
||||
EndPoint downstreamEndPoint = connectContext.getEndPoint();
|
||||
DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context);
|
||||
downstreamConnection.setInputBufferSize(getBufferSize());
|
||||
|
||||
|
@ -370,11 +375,10 @@ public class ConnectHandler extends HandlerWrapper
|
|||
response.setContentLength(0);
|
||||
if (statusCode != HttpServletResponse.SC_OK)
|
||||
response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
|
||||
response.getOutputStream().close();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("CONNECT response sent {} {}", request.getProtocol(), response.getStatus());
|
||||
}
|
||||
catch (IOException x)
|
||||
catch (Throwable x)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Could not send CONNECT response", x);
|
||||
|
@ -411,10 +415,9 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection)
|
||||
{
|
||||
// Set the new connection as request attribute and change the status to 101
|
||||
// so that Jetty understands that it has to upgrade the connection
|
||||
request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, connection);
|
||||
response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
// Set the new connection as request attribute so that
|
||||
// Jetty understands that it has to upgrade the connection.
|
||||
request.setAttribute(HttpTransport.UPGRADE_CONNECTION_ATTRIBUTE, connection);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Upgraded connection to {}", connection);
|
||||
}
|
||||
|
@ -501,11 +504,11 @@ public class ConnectHandler extends HandlerWrapper
|
|||
}
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) throws IOException
|
||||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
|
||||
{
|
||||
SocketChannelEndPoint endp = new SocketChannelEndPoint(channel, selector, key, getScheduler());
|
||||
endp.setIdleTimeout(getIdleTimeout());
|
||||
return endp;
|
||||
SocketChannelEndPoint endPoint = new SocketChannelEndPoint(channel, selector, key, getScheduler());
|
||||
endPoint.setIdleTimeout(getIdleTimeout());
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -534,14 +537,14 @@ public class ConnectHandler extends HandlerWrapper
|
|||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
private final AsyncContext asyncContext;
|
||||
private final HttpConnection httpConnection;
|
||||
private final EndPoint endPoint;
|
||||
|
||||
public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, HttpConnection httpConnection)
|
||||
public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, EndPoint endPoint)
|
||||
{
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.asyncContext = asyncContext;
|
||||
this.httpConnection = httpConnection;
|
||||
this.endPoint = endPoint;
|
||||
}
|
||||
|
||||
public ConcurrentMap<String, Object> getContext()
|
||||
|
@ -564,9 +567,9 @@ public class ConnectHandler extends HandlerWrapper
|
|||
return asyncContext;
|
||||
}
|
||||
|
||||
public HttpConnection getHttpConnection()
|
||||
public EndPoint getEndPoint()
|
||||
{
|
||||
return httpConnection;
|
||||
return endPoint;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -603,7 +606,7 @@ public class ConnectHandler extends HandlerWrapper
|
|||
|
||||
public class DownstreamConnection extends ProxyConnection implements Connection.UpgradeTo
|
||||
{
|
||||
private ByteBuffer buffer;
|
||||
private ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
|
||||
|
||||
public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
|
||||
{
|
||||
|
@ -613,7 +616,8 @@ public class ConnectHandler extends HandlerWrapper
|
|||
@Override
|
||||
public void onUpgradeTo(ByteBuffer buffer)
|
||||
{
|
||||
this.buffer = buffer == null ? BufferUtil.EMPTY_BUFFER : buffer;
|
||||
if (buffer != null)
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -37,7 +37,7 @@ public abstract class ProxyConnection extends AbstractConnection
|
|||
private final IteratingCallback pipe = new ProxyIteratingCallback();
|
||||
private final ByteBufferPool bufferPool;
|
||||
private final ConcurrentMap<String, Object> context;
|
||||
private Connection connection;
|
||||
private ProxyConnection connection;
|
||||
|
||||
protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ public abstract class ProxyConnection extends AbstractConnection
|
|||
return connection;
|
||||
}
|
||||
|
||||
public void setConnection(Connection connection)
|
||||
public void setConnection(ProxyConnection connection)
|
||||
{
|
||||
this.connection = connection;
|
||||
}
|
||||
|
@ -76,6 +76,11 @@ public abstract class ProxyConnection extends AbstractConnection
|
|||
|
||||
protected abstract void write(EndPoint endPoint, ByteBuffer buffer, Callback callback);
|
||||
|
||||
protected void close(Throwable failure)
|
||||
{
|
||||
getEndPoint().close(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toConnectionString()
|
||||
{
|
||||
|
@ -92,7 +97,7 @@ public abstract class ProxyConnection extends AbstractConnection
|
|||
private int filled;
|
||||
|
||||
@Override
|
||||
protected Action process() throws Exception
|
||||
protected Action process()
|
||||
{
|
||||
buffer = bufferPool.acquire(getInputBufferSize(), true);
|
||||
try
|
||||
|
@ -123,7 +128,7 @@ public abstract class ProxyConnection extends AbstractConnection
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(ProxyConnection.this + " could not fill", x);
|
||||
bufferPool.release(buffer);
|
||||
disconnect();
|
||||
disconnect(x);
|
||||
return Action.SUCCEEDED;
|
||||
}
|
||||
}
|
||||
|
@ -147,14 +152,14 @@ public abstract class ProxyConnection extends AbstractConnection
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(ProxyConnection.this + " failed to write " + filled + " bytes", x);
|
||||
disconnect();
|
||||
bufferPool.release(buffer);
|
||||
disconnect(x);
|
||||
}
|
||||
|
||||
private void disconnect()
|
||||
private void disconnect(Throwable x)
|
||||
{
|
||||
bufferPool.release(buffer);
|
||||
ProxyConnection.this.close();
|
||||
connection.close();
|
||||
ProxyConnection.this.close(x);
|
||||
connection.close(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,9 +55,9 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
private static final String CONTINUE_ACTION_ATTRIBUTE = ProxyServlet.class.getName() + ".continueAction";
|
||||
|
||||
@Override
|
||||
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
final int requestId = getRequestId(request);
|
||||
int requestId = getRequestId(request);
|
||||
|
||||
String rewrittenTarget = rewriteTarget(request);
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
return;
|
||||
}
|
||||
|
||||
final Request proxyRequest = getHttpClient().newRequest(rewrittenTarget)
|
||||
Request proxyRequest = getHttpClient().newRequest(rewrittenTarget)
|
||||
.method(request.getMethod())
|
||||
.version(HttpVersion.fromString(request.getProtocol()));
|
||||
|
||||
|
@ -84,7 +84,7 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
|
||||
addProxyHeaders(request, proxyRequest);
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync();
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
// We do not timeout the continuation, but the proxy request
|
||||
asyncContext.setTimeout(0);
|
||||
proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
|
||||
|
@ -200,7 +200,7 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback)
|
||||
public void onContent(Response proxyResponse, ByteBuffer content, Callback callback)
|
||||
{
|
||||
byte[] buffer;
|
||||
int offset;
|
||||
|
|
|
@ -300,7 +300,7 @@ public class ForwardProxyTLSServerTest
|
|||
.path("/echo?body=" + URLEncoder.encode(content1, "UTF-8"))
|
||||
.onRequestCommit(request ->
|
||||
{
|
||||
Destination destination = httpClient.getDestination(HttpScheme.HTTPS.asString(), "localhost", serverConnector.getLocalPort());
|
||||
Destination destination = httpClient.resolveDestination(request);
|
||||
destination.newConnection(new Promise.Adapter<>()
|
||||
{
|
||||
@Override
|
||||
|
|
|
@ -214,13 +214,12 @@ public class ProxyServletTest
|
|||
// Shutdown the proxy
|
||||
proxy.stop();
|
||||
|
||||
ExecutionException x = assertThrows(ExecutionException.class,
|
||||
() ->
|
||||
{
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
});
|
||||
ExecutionException x = assertThrows(ExecutionException.class, () ->
|
||||
{
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
});
|
||||
assertThat(x.getCause(), instanceOf(ConnectException.class));
|
||||
}
|
||||
|
||||
|
@ -231,7 +230,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -258,7 +257,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -292,7 +291,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -322,7 +321,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -362,7 +361,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -417,7 +416,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
try (InputStream input = Files.newInputStream(temp))
|
||||
{
|
||||
|
@ -466,7 +465,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
resp.getOutputStream().print(req.getQueryString());
|
||||
}
|
||||
|
@ -490,7 +489,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
|
||||
{
|
||||
if (!request.isAsyncStarted())
|
||||
{
|
||||
|
@ -499,12 +498,12 @@ public class ProxyServletTest
|
|||
asyncContext.addListener(new AsyncListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException
|
||||
public void onComplete(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
public void onTimeout(AsyncEvent event)
|
||||
{
|
||||
if (request.getHeader("Via") != null)
|
||||
response.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -512,12 +511,12 @@ public class ProxyServletTest
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException
|
||||
public void onError(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException
|
||||
public void onStartAsync(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
@ -541,7 +540,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
PrintWriter writer = resp.getWriter();
|
||||
writer.write(req.getHeader("X-Forwarded-Host"));
|
||||
|
@ -610,7 +609,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -665,7 +664,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -722,7 +721,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -767,7 +766,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -808,7 +807,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -840,7 +839,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -875,7 +874,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -902,7 +901,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -932,7 +931,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
|
||||
{
|
||||
byte[] message = "tooshort".getBytes("ascii");
|
||||
resp.setContentType("text/plain;charset=ascii");
|
||||
|
@ -964,7 +963,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
|
@ -1036,7 +1035,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
ServletOutputStream output = response.getOutputStream();
|
||||
output.write(chunk1);
|
||||
|
@ -1069,7 +1068,8 @@ public class ProxyServletTest
|
|||
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
int port = serverConnector.getLocalPort();
|
||||
client.newRequest("localhost", port).send(listener);
|
||||
Request request = client.newRequest("localhost", port);
|
||||
request.send(listener);
|
||||
|
||||
// Make the proxy request fail; given the small content, the
|
||||
// proxy-to-client response is not committed yet so it will be reset.
|
||||
|
@ -1091,7 +1091,7 @@ public class ProxyServletTest
|
|||
// Make sure the proxy does not receive chunk2.
|
||||
assertEquals(-1, input.read());
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
assertTrue(connectionPool.isEmpty());
|
||||
}
|
||||
|
@ -1101,14 +1101,14 @@ public class ProxyServletTest
|
|||
public void testProxyRequestFailureInTheMiddleOfProxyingBigContent(Class<? extends ProxyServlet> proxyServletClass) throws Exception
|
||||
{
|
||||
int outputBufferSize = 1024;
|
||||
final CountDownLatch chunk1Latch = new CountDownLatch(1);
|
||||
final byte[] chunk1 = new byte[outputBufferSize];
|
||||
CountDownLatch chunk1Latch = new CountDownLatch(1);
|
||||
byte[] chunk1 = new byte[outputBufferSize];
|
||||
new Random().nextBytes(chunk1);
|
||||
final int chunk2 = 'w';
|
||||
int chunk2 = 'w';
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
ServletOutputStream output = response.getOutputStream();
|
||||
output.write(chunk1);
|
||||
|
@ -1142,29 +1142,29 @@ public class ProxyServletTest
|
|||
|
||||
InputStreamResponseListener listener = new InputStreamResponseListener();
|
||||
int port = serverConnector.getLocalPort();
|
||||
client.newRequest("localhost", port).send(listener);
|
||||
Request request = client.newRequest("localhost", port);
|
||||
request.send(listener);
|
||||
|
||||
Response response = listener.get(5, TimeUnit.SECONDS);
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
InputStream input = listener.getInputStream();
|
||||
for (int i = 0; i < chunk1.length; ++i)
|
||||
for (byte b : chunk1)
|
||||
{
|
||||
assertEquals(chunk1[i] & 0xFF, input.read());
|
||||
assertEquals(b & 0xFF, input.read());
|
||||
}
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(2 * proxyTimeout);
|
||||
|
||||
chunk1Latch.countDown();
|
||||
|
||||
assertThrows(EOFException.class,
|
||||
() ->
|
||||
{
|
||||
// Make sure the proxy does not receive chunk2.
|
||||
input.read();
|
||||
});
|
||||
assertThrows(EOFException.class, () ->
|
||||
{
|
||||
// Make sure the proxy does not receive chunk2.
|
||||
input.read();
|
||||
});
|
||||
|
||||
HttpDestination destination = (HttpDestination)client.getDestination("http", "localhost", port);
|
||||
HttpDestination destination = (HttpDestination)client.resolveDestination(request);
|
||||
ConnectionPool connectionPool = destination.getConnectionPool();
|
||||
assertTrue(connectionPool.isEmpty());
|
||||
}
|
||||
|
@ -1181,7 +1181,7 @@ public class ProxyServletTest
|
|||
proxyContext.addFilter(new FilterHolder(new Filter()
|
||||
{
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException
|
||||
public void init(FilterConfig filterConfig)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1221,7 +1221,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
List<String> names = Collections.list(request.getHeaderNames());
|
||||
for (String name : names)
|
||||
|
@ -1255,7 +1255,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
serverLatch1.countDown();
|
||||
|
||||
|
@ -1321,7 +1321,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
|
@ -1373,7 +1373,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
|
@ -1423,7 +1423,7 @@ public class ProxyServletTest
|
|||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
serverLatch1.countDown();
|
||||
|
||||
|
|
|
@ -441,8 +441,9 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
private MethodHandle getLogHandle(String formatString) throws NoSuchMethodException, IllegalAccessException
|
||||
{
|
||||
MethodHandle append = MethodHandles.lookup().findStatic(CustomRequestLog.class, "append", methodType(Void.TYPE, String.class, StringBuilder.class));
|
||||
MethodHandle logHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, "logNothing", methodType(Void.TYPE, StringBuilder.class, Request.class, Response.class));
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
MethodHandle append = lookup.findStatic(CustomRequestLog.class, "append", methodType(Void.TYPE, String.class, StringBuilder.class));
|
||||
MethodHandle logHandle = lookup.findStatic(CustomRequestLog.class, "logNothing", methodType(Void.TYPE, StringBuilder.class, Request.class, Response.class));
|
||||
|
||||
List<Token> tokens = getTokens(formatString);
|
||||
Collections.reverse(tokens);
|
||||
|
@ -452,7 +453,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (t.isLiteralString())
|
||||
logHandle = updateLogHandle(logHandle, append, t.literal);
|
||||
else
|
||||
logHandle = updateLogHandle(logHandle, append, t.code, t.arg, t.modifiers, t.negated);
|
||||
logHandle = updateLogHandle(logHandle, append, lookup, t.code, t.arg, t.modifiers, t.negated);
|
||||
}
|
||||
|
||||
return logHandle;
|
||||
|
@ -578,7 +579,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
return foldArguments(logHandle, dropArguments(dropArguments(append.bindTo(literal), 1, Request.class), 2, Response.class));
|
||||
}
|
||||
|
||||
private MethodHandle updateLogHandle(MethodHandle logHandle, MethodHandle append, String code, String arg, List<String> modifiers, boolean negated) throws NoSuchMethodException, IllegalAccessException
|
||||
private MethodHandle updateLogHandle(MethodHandle logHandle, MethodHandle append, MethodHandles.Lookup lookup, String code, String arg, List<String> modifiers, boolean negated) throws NoSuchMethodException, IllegalAccessException
|
||||
{
|
||||
MethodType logType = methodType(Void.TYPE, StringBuilder.class, Request.class, Response.class);
|
||||
MethodType logTypeArg = methodType(Void.TYPE, String.class, StringBuilder.class, Request.class, Response.class);
|
||||
|
@ -621,7 +622,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
throw new IllegalArgumentException("Invalid arg for %a");
|
||||
}
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -654,7 +655,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
throw new IllegalArgumentException("Invalid arg for %p");
|
||||
}
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -668,7 +669,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
else
|
||||
throw new IllegalArgumentException("Invalid argument for %I");
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -682,7 +683,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
else
|
||||
throw new IllegalArgumentException("Invalid argument for %O");
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -696,7 +697,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
else
|
||||
throw new IllegalArgumentException("Invalid argument for %S");
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -704,13 +705,11 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
{
|
||||
String method = "logRequestCookies";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestCookies", logType);
|
||||
}
|
||||
else
|
||||
{
|
||||
String method = "logRequestCookie";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeArg);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestCookie", logTypeArg);
|
||||
specificHandle = specificHandle.bindTo(arg);
|
||||
}
|
||||
break;
|
||||
|
@ -718,8 +717,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "D":
|
||||
{
|
||||
String method = "logLatencyMicroseconds";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logLatencyMicroseconds", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -728,23 +726,20 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (arg == null || arg.isEmpty())
|
||||
throw new IllegalArgumentException("No arg for %e");
|
||||
|
||||
String method = "logEnvironmentVar";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeArg);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logEnvironmentVar", logTypeArg);
|
||||
specificHandle = specificHandle.bindTo(arg);
|
||||
break;
|
||||
}
|
||||
|
||||
case "f":
|
||||
{
|
||||
String method = "logFilename";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logFilename", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "H":
|
||||
{
|
||||
String method = "logRequestProtocol";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestProtocol", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -753,23 +748,20 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (arg == null || arg.isEmpty())
|
||||
throw new IllegalArgumentException("No arg for %i");
|
||||
|
||||
String method = "logRequestHeader";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeArg);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestHeader", logTypeArg);
|
||||
specificHandle = specificHandle.bindTo(arg);
|
||||
break;
|
||||
}
|
||||
|
||||
case "k":
|
||||
{
|
||||
String method = "logKeepAliveRequests";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logKeepAliveRequests", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "m":
|
||||
{
|
||||
String method = "logRequestMethod";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestMethod", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -778,37 +770,32 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (arg == null || arg.isEmpty())
|
||||
throw new IllegalArgumentException("No arg for %o");
|
||||
|
||||
String method = "logResponseHeader";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeArg);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logResponseHeader", logTypeArg);
|
||||
specificHandle = specificHandle.bindTo(arg);
|
||||
break;
|
||||
}
|
||||
|
||||
case "q":
|
||||
{
|
||||
String method = "logQueryString";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logQueryString", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "r":
|
||||
{
|
||||
String method = "logRequestFirstLine";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestFirstLine", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "R":
|
||||
{
|
||||
String method = "logRequestHandler";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestHandler", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "s":
|
||||
{
|
||||
String method = "logResponseStatus";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logResponseStatus", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -845,9 +832,8 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
DateCache logDateCache = new DateCache(format, locale, timeZone);
|
||||
|
||||
String method = "logRequestTime";
|
||||
MethodType logTypeDateCache = methodType(Void.TYPE, DateCache.class, StringBuilder.class, Request.class, Response.class);
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeDateCache);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestTime", logTypeDateCache);
|
||||
specificHandle = specificHandle.bindTo(logDateCache);
|
||||
break;
|
||||
}
|
||||
|
@ -873,7 +859,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
throw new IllegalArgumentException("Invalid arg for %T");
|
||||
}
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -885,21 +871,19 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
else
|
||||
method = "logRequestAuthentication";
|
||||
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "U":
|
||||
{
|
||||
String method = "logUrlRequestPath";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logUrlRequestPath", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
case "X":
|
||||
{
|
||||
String method = "logConnectionStatus";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logType);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logConnectionStatus", logType);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -908,8 +892,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (arg == null || arg.isEmpty())
|
||||
throw new IllegalArgumentException("No arg for %ti");
|
||||
|
||||
String method = "logRequestTrailer";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeArg);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestTrailer", logTypeArg);
|
||||
specificHandle = specificHandle.bindTo(arg);
|
||||
break;
|
||||
}
|
||||
|
@ -919,8 +902,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (arg == null || arg.isEmpty())
|
||||
throw new IllegalArgumentException("No arg for %to");
|
||||
|
||||
String method = "logResponseTrailer";
|
||||
specificHandle = MethodHandles.lookup().findStatic(CustomRequestLog.class, method, logTypeArg);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logResponseTrailer", logTypeArg);
|
||||
specificHandle = specificHandle.bindTo(arg);
|
||||
break;
|
||||
}
|
||||
|
@ -931,7 +913,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
if (modifiers != null && !modifiers.isEmpty())
|
||||
{
|
||||
MethodHandle modifierTest = MethodHandles.lookup().findStatic(CustomRequestLog.class, "modify", methodType(Boolean.TYPE, List.class, Boolean.class, StringBuilder.class, Request.class, Response.class));
|
||||
MethodHandle modifierTest = lookup.findStatic(CustomRequestLog.class, "modify", methodType(Boolean.TYPE, List.class, Boolean.class, StringBuilder.class, Request.class, Response.class));
|
||||
|
||||
MethodHandle dash = updateLogHandle(logHandle, append, "-");
|
||||
MethodHandle log = foldArguments(logHandle, specificHandle);
|
||||
|
|
|
@ -500,13 +500,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
status == HttpStatus.NO_CONTENT_204 ||
|
||||
status == HttpStatus.NOT_MODIFIED_304);
|
||||
if (hasContent && !_response.isContentComplete(_response.getHttpOutput().getWritten()))
|
||||
{
|
||||
if (isCommitted())
|
||||
abort(new IOException("insufficient content written"));
|
||||
else
|
||||
_response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "insufficient content written");
|
||||
}
|
||||
sendErrorOrAbort("Insufficient content written");
|
||||
}
|
||||
checkAndPrepareUpgrade();
|
||||
_response.closeOutput();
|
||||
}
|
||||
finally
|
||||
|
@ -543,6 +539,22 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
return !suspended;
|
||||
}
|
||||
|
||||
public void sendErrorOrAbort(String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isCommitted())
|
||||
abort(new IOException(message));
|
||||
else
|
||||
_response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, message);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.ignore(x);
|
||||
abort(x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendError(int code, String reason)
|
||||
{
|
||||
try
|
||||
|
@ -635,7 +647,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
int code = 500;
|
||||
Integer status = (Integer)_request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
|
||||
if (status != null)
|
||||
{
|
||||
code = status.intValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
Throwable cause = unwrap(failure, BadMessageException.class);
|
||||
|
@ -740,6 +754,17 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks whether the processing of the request resulted in an upgrade,
|
||||
* and if so performs upgrade preparation steps <em>before</em> the upgrade
|
||||
* response is sent back to the client.</p>
|
||||
* <p>This avoids a race where the server is unprepared if the client sends
|
||||
* data immediately after having received the upgrade response.</p>
|
||||
*/
|
||||
protected void checkAndPrepareUpgrade()
|
||||
{
|
||||
}
|
||||
|
||||
public void onCompleted()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -816,13 +841,13 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean sendResponse(MetaData.Response info, ByteBuffer content, boolean complete, final Callback callback)
|
||||
protected boolean sendResponse(MetaData.Response response, ByteBuffer content, boolean complete, final Callback callback)
|
||||
{
|
||||
boolean committing = _committed.compareAndSet(false, true);
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("sendResponse info={} content={} complete={} committing={} callback={}",
|
||||
info,
|
||||
response,
|
||||
BufferUtil.toDetailString(content),
|
||||
complete,
|
||||
committing,
|
||||
|
@ -831,23 +856,23 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
if (committing)
|
||||
{
|
||||
// We need an info to commit
|
||||
if (info == null)
|
||||
info = _response.newResponseMetaData();
|
||||
commit(info);
|
||||
if (response == null)
|
||||
response = _response.newResponseMetaData();
|
||||
commit(response);
|
||||
|
||||
// wrap callback to process 100 responses
|
||||
final int status = info.getStatus();
|
||||
final Callback committed = (status < 200 && status >= 100) ? new Send100Callback(callback) : new SendCallback(callback, content, true, complete);
|
||||
// Wrap the callback to process 1xx responses.
|
||||
Callback committed = HttpStatus.isInformational(response.getStatus())
|
||||
? new Send100Callback(callback) : new SendCallback(callback, content, true, complete);
|
||||
|
||||
notifyResponseBegin(_request);
|
||||
|
||||
// committing write
|
||||
_transport.send(info, _request.isHead(), content, complete, committed);
|
||||
_transport.send(_request.getMetaData(), response, content, complete, committed);
|
||||
}
|
||||
else if (info == null)
|
||||
else if (response == null)
|
||||
{
|
||||
// This is a normal write
|
||||
_transport.send(null, _request.isHead(), content, complete, new SendCallback(callback, content, false, complete));
|
||||
_transport.send(_request.getMetaData(), null, content, complete, new SendCallback(callback, content, false, complete));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -964,6 +989,16 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
_transport.abort(failure);
|
||||
}
|
||||
|
||||
public boolean isTunnellingSupported()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public EndPoint getTunnellingEndPoint()
|
||||
{
|
||||
throw new UnsupportedOperationException("Tunnelling not supported");
|
||||
}
|
||||
|
||||
private void notifyRequestBegin(Request request)
|
||||
{
|
||||
notifyEvent1(listener -> listener::onRequestBegin, request);
|
||||
|
@ -1290,7 +1325,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
|
||||
if (x instanceof BadMessageException)
|
||||
{
|
||||
_transport.send(HttpGenerator.RESPONSE_500_INFO, false, null, true, new Callback.Nested(this)
|
||||
_transport.send(_request.getMetaData(), HttpGenerator.RESPONSE_500_INFO, null, true, new Callback.Nested(this)
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
|
|
|
@ -235,7 +235,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
if (_metadata.getMethod() == null)
|
||||
_httpConnection.close();
|
||||
else if (onEarlyEOF() || _delayedForContent)
|
||||
{
|
||||
{
|
||||
_delayedForContent = false;
|
||||
handle();
|
||||
}
|
||||
|
@ -289,6 +289,8 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
@Override
|
||||
public boolean headerComplete()
|
||||
{
|
||||
onRequest(_metadata);
|
||||
|
||||
if (_complianceViolations != null && !_complianceViolations.isEmpty())
|
||||
{
|
||||
this.getRequest().setAttribute(HttpCompliance.VIOLATIONS_ATTR, _complianceViolations);
|
||||
|
@ -369,9 +371,9 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
_upgrade = PREAMBLE_UPGRADE_H2C;
|
||||
|
||||
if (HttpMethod.PRI.is(_metadata.getMethod()) &&
|
||||
"*".equals(_metadata.getURI().toString()) &&
|
||||
_fields.size() == 0 &&
|
||||
upgrade())
|
||||
"*".equals(_metadata.getURI().getPath()) &&
|
||||
_fields.size() == 0 &&
|
||||
upgrade())
|
||||
return true;
|
||||
|
||||
badMessage(new BadMessageException(HttpStatus.UPGRADE_REQUIRED_426));
|
||||
|
@ -388,8 +390,6 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
if (!persistent)
|
||||
_httpConnection.getGenerator().setPersistent(false);
|
||||
|
||||
onRequest(_metadata);
|
||||
|
||||
// Should we delay dispatch until we have some content?
|
||||
// We should not delay if there is no content expect or client is expecting 100 or the response is already committed or the request buffer already has something in it to parse
|
||||
_delayedForContent = (getHttpConfiguration().isDelayDispatchUntilContent() &&
|
||||
|
@ -478,8 +478,8 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Upgrade from {} to {}", getEndPoint().getConnection(), upgradeConnection);
|
||||
getRequest().setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, upgradeConnection);
|
||||
getResponse().setStatus(101);
|
||||
getRequest().setAttribute(HttpTransport.UPGRADE_CONNECTION_ATTRIBUTE, upgradeConnection);
|
||||
getResponse().setStatus(HttpStatus.SWITCHING_PROTOCOLS_101);
|
||||
getHttpTransport().onCompleted();
|
||||
return true;
|
||||
}
|
||||
|
@ -536,10 +536,22 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
_complianceViolations = new ArrayList<>();
|
||||
}
|
||||
String record = String.format("%s (see %s) in mode %s for %s in %s",
|
||||
violation.getDescription(), violation.getURL(), mode, details, getHttpTransport());
|
||||
violation.getDescription(), violation.getURL(), mode, details, getHttpTransport());
|
||||
_complianceViolations.add(record);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTunnellingSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EndPoint getTunnellingEndPoint()
|
||||
{
|
||||
return getEndPoint();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,9 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpParser;
|
||||
import org.eclipse.jetty.http.HttpParser.RequestHandler;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
|
@ -54,7 +54,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpConnection.class);
|
||||
public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
public static final String UPGRADE_CONNECTION_ATTRIBUTE = "org.eclipse.jetty.server.HttpConnection.UPGRADE";
|
||||
private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
|
||||
|
||||
private final HttpConfiguration _config;
|
||||
|
@ -317,7 +316,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
LOG.debug("{} onFillable exit {} {}", this, _channel.getState(), BufferUtil.toDetailString(_requestBuffer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fill and parse data looking for content
|
||||
*
|
||||
|
@ -394,33 +393,38 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
return handle;
|
||||
}
|
||||
|
||||
private boolean upgrade()
|
||||
{
|
||||
Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
|
||||
if (connection == null)
|
||||
return false;
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Upgrade from {} to {}", this, connection);
|
||||
_channel.getState().upgrade();
|
||||
getEndPoint().upgrade(connection);
|
||||
_channel.recycle();
|
||||
_parser.reset();
|
||||
_generator.reset();
|
||||
if (_contentBufferReferences.get() == 0)
|
||||
{
|
||||
releaseRequestBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("{} lingering content references?!?!", this);
|
||||
_requestBuffer = null; // Not returned to pool!
|
||||
_contentBufferReferences.set(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted()
|
||||
{
|
||||
// Handle connection upgrades
|
||||
if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
|
||||
{
|
||||
Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
|
||||
if (connection != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Upgrade from {} to {}", this, connection);
|
||||
_channel.getState().upgrade();
|
||||
getEndPoint().upgrade(connection);
|
||||
_channel.recycle();
|
||||
_parser.reset();
|
||||
_generator.reset();
|
||||
if (_contentBufferReferences.get() == 0)
|
||||
releaseRequestBuffer();
|
||||
else
|
||||
{
|
||||
LOG.warn("{} lingering content references?!?!", this);
|
||||
_requestBuffer = null; // Not returned to pool!
|
||||
_contentBufferReferences.set(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Handle connection upgrades.
|
||||
if (upgrade())
|
||||
return;
|
||||
|
||||
// Finish consuming the request
|
||||
// If we are still expecting
|
||||
|
@ -545,9 +549,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
|
||||
@Override
|
||||
public void send(MetaData.Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
if (info == null)
|
||||
if (response == null)
|
||||
{
|
||||
if (!lastContent && BufferUtil.isEmpty(content))
|
||||
{
|
||||
|
@ -563,7 +567,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
_generator.setPersistent(false);
|
||||
}
|
||||
|
||||
if (_sendCallback.reset(info, head, content, lastContent, callback))
|
||||
if (_sendCallback.reset(request, response, content, lastContent, callback))
|
||||
{
|
||||
_sendCallback.iterate();
|
||||
}
|
||||
|
@ -635,11 +639,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
public String toConnectionString()
|
||||
{
|
||||
return String.format("%s@%x[p=%s,g=%s]=>%s",
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
_parser,
|
||||
_generator,
|
||||
_channel);
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
_parser,
|
||||
_generator,
|
||||
_channel);
|
||||
}
|
||||
|
||||
private class Content extends HttpInput.Content
|
||||
|
@ -725,21 +729,21 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
return _callback.getInvocationType();
|
||||
}
|
||||
|
||||
private boolean reset(MetaData.Response info, boolean head, ByteBuffer content, boolean last, Callback callback)
|
||||
private boolean reset(MetaData.Request request, MetaData.Response info, ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
if (reset())
|
||||
{
|
||||
_info = info;
|
||||
_head = head;
|
||||
_head = HttpMethod.HEAD.is(request.getMethod());
|
||||
_content = content;
|
||||
_lastContent = last;
|
||||
_callback = callback;
|
||||
_header = null;
|
||||
_shutdownOut = false;
|
||||
|
||||
|
||||
if (getConnector().isShutdown())
|
||||
_generator.setPersistent(false);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -774,7 +778,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
{
|
||||
case NEED_INFO:
|
||||
throw new EofException("request lifecycle violation");
|
||||
|
||||
|
||||
case NEED_HEADER:
|
||||
{
|
||||
_header = _bufferPool.acquire(_config.getResponseHeaderSize(), useDirectBuffers);
|
||||
|
@ -800,7 +804,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
BufferUtil.clear(chunk);
|
||||
BufferUtil.clear(_content);
|
||||
}
|
||||
|
||||
|
||||
byte gatherWrite = 0;
|
||||
long bytes = 0;
|
||||
if (BufferUtil.hasContent(_header))
|
||||
|
@ -843,9 +847,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
getEndPoint().write(this, _content);
|
||||
break;
|
||||
default:
|
||||
succeeded();
|
||||
succeeded();
|
||||
}
|
||||
|
||||
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
case SHUTDOWN_OUT:
|
||||
|
@ -854,7 +858,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
continue;
|
||||
}
|
||||
case DONE:
|
||||
{
|
||||
{
|
||||
// If shutdown after commit, we can still close here.
|
||||
if (getConnector().isShutdown())
|
||||
_shutdownOut = true;
|
||||
|
|
|
@ -28,17 +28,19 @@ import org.eclipse.jetty.util.Callback;
|
|||
*/
|
||||
public interface HttpTransport
|
||||
{
|
||||
String UPGRADE_CONNECTION_ATTRIBUTE = HttpTransport.class.getName() + ".UPGRADE";
|
||||
|
||||
/**
|
||||
* Asynchronous call to send a response (or part) over the transport
|
||||
*
|
||||
* @param info The header info to send, or null if just sending more data.
|
||||
* The first call to send for a response must have a non null info.
|
||||
* @param head True if the response if for a HEAD request (and the data should not be sent).
|
||||
* @param request True if the response if for a HEAD request (and the data should not be sent).
|
||||
* @param response The header info to send, or null if just sending more data.
|
||||
* The first call to send for a response must have a non null info.
|
||||
* @param content A buffer of content to be sent.
|
||||
* @param lastContent True if the content is the last content for the current response.
|
||||
* @param callback The Callback instance that success or failure of the send is notified on
|
||||
*/
|
||||
void send(MetaData.Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback);
|
||||
void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback);
|
||||
|
||||
/**
|
||||
* @return true if responses can be pushed over this transport
|
||||
|
@ -55,7 +57,7 @@ public interface HttpTransport
|
|||
* some time after the last content is sent).
|
||||
*/
|
||||
void onCompleted();
|
||||
|
||||
|
||||
/**
|
||||
* Aborts this transport.
|
||||
* <p>
|
||||
|
|
|
@ -58,6 +58,7 @@ import javax.servlet.ServletResponse;
|
|||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletMapping;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpUpgradeHandler;
|
||||
|
@ -484,57 +485,30 @@ public class Request implements HttpServletRequest
|
|||
{
|
||||
try
|
||||
{
|
||||
int maxFormContentSize = -1;
|
||||
int maxFormKeys = -1;
|
||||
int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE;
|
||||
int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS;
|
||||
|
||||
if (_context != null)
|
||||
{
|
||||
maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
|
||||
maxFormKeys = _context.getContextHandler().getMaxFormKeys();
|
||||
ContextHandler contextHandler = _context.getContextHandler();
|
||||
maxFormContentSize = contextHandler.getMaxFormContentSize();
|
||||
maxFormKeys = contextHandler.getMaxFormKeys();
|
||||
}
|
||||
|
||||
if (maxFormContentSize < 0)
|
||||
else
|
||||
{
|
||||
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
|
||||
if (obj == null)
|
||||
maxFormContentSize = 200000;
|
||||
else if (obj instanceof Number)
|
||||
{
|
||||
Number size = (Number)obj;
|
||||
maxFormContentSize = size.intValue();
|
||||
}
|
||||
else if (obj instanceof String)
|
||||
{
|
||||
maxFormContentSize = Integer.parseInt((String)obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (maxFormKeys < 0)
|
||||
{
|
||||
Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
|
||||
if (obj == null)
|
||||
maxFormKeys = 1000;
|
||||
else if (obj instanceof Number)
|
||||
{
|
||||
Number keys = (Number)obj;
|
||||
maxFormKeys = keys.intValue();
|
||||
}
|
||||
else if (obj instanceof String)
|
||||
{
|
||||
maxFormKeys = Integer.parseInt((String)obj);
|
||||
}
|
||||
maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize);
|
||||
maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys);
|
||||
}
|
||||
|
||||
int contentLength = getContentLength();
|
||||
if (contentLength > maxFormContentSize && maxFormContentSize > 0)
|
||||
{
|
||||
throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize);
|
||||
}
|
||||
if (maxFormContentSize >= 0 && contentLength > maxFormContentSize)
|
||||
throw new IllegalStateException("Form is larger than max length " + maxFormContentSize);
|
||||
|
||||
InputStream in = getInputStream();
|
||||
if (_input.isAsync())
|
||||
throw new IllegalStateException("Cannot extract parameters with async IO");
|
||||
|
||||
UrlEncoded.decodeTo(in, params, getCharacterEncoding(), contentLength < 0 ? maxFormContentSize : -1, maxFormKeys);
|
||||
UrlEncoded.decodeTo(in, params, getCharacterEncoding(), maxFormContentSize, maxFormKeys);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -543,6 +517,16 @@ public class Request implements HttpServletRequest
|
|||
}
|
||||
}
|
||||
|
||||
private int lookupServerAttribute(String key, int dftValue)
|
||||
{
|
||||
Object attribute = _channel.getServer().getAttribute(key);
|
||||
if (attribute instanceof Number)
|
||||
return ((Number)attribute).intValue();
|
||||
else if (attribute instanceof String)
|
||||
return Integer.parseInt((String)attribute);
|
||||
return dftValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncContext getAsyncContext()
|
||||
{
|
||||
|
@ -2137,7 +2121,7 @@ public class Request implements HttpServletRequest
|
|||
AsyncContextEvent event = new AsyncContextEvent(_context, _async, state, this, servletRequest, servletResponse);
|
||||
event.setDispatchContext(getServletContext());
|
||||
|
||||
String uri = ((HttpServletRequest)servletRequest).getRequestURI();
|
||||
String uri = unwrap(servletRequest).getRequestURI();
|
||||
if (_contextPath != null && uri.startsWith(_contextPath))
|
||||
uri = uri.substring(_contextPath.length());
|
||||
else
|
||||
|
@ -2149,6 +2133,19 @@ public class Request implements HttpServletRequest
|
|||
return _async;
|
||||
}
|
||||
|
||||
public static HttpServletRequest unwrap(ServletRequest servletRequest)
|
||||
{
|
||||
if (servletRequest instanceof HttpServletRequestWrapper)
|
||||
{
|
||||
return (HttpServletRequestWrapper)servletRequest;
|
||||
}
|
||||
if (servletRequest instanceof ServletRequestWrapper)
|
||||
{
|
||||
return unwrap(((ServletRequestWrapper)servletRequest).getRequest());
|
||||
}
|
||||
return ((HttpServletRequest)servletRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
|
|
@ -32,8 +32,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
import java.util.function.Supplier;
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletResponseWrapper;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.eclipse.jetty.http.CookieCompliance;
|
||||
|
@ -1292,6 +1295,19 @@ public class Response implements HttpServletResponse
|
|||
}
|
||||
}
|
||||
|
||||
public static HttpServletResponse unwrap(ServletResponse servletResponse)
|
||||
{
|
||||
if (servletResponse instanceof HttpServletResponseWrapper)
|
||||
{
|
||||
return (HttpServletResponseWrapper)servletResponse;
|
||||
}
|
||||
if (servletResponse instanceof ServletResponseWrapper)
|
||||
{
|
||||
return unwrap(((ServletResponseWrapper)servletResponse).getResponse());
|
||||
}
|
||||
return (HttpServletResponse)servletResponse;
|
||||
}
|
||||
|
||||
private static class HttpFieldsSupplier implements Supplier<HttpFields>
|
||||
{
|
||||
private final Supplier<Map<String, String>> _supplier;
|
||||
|
|
|
@ -558,8 +558,8 @@ public class Server extends HandlerWrapper implements Attributes
|
|||
}
|
||||
|
||||
final String target = baseRequest.getPathInfo();
|
||||
final HttpServletRequest request = (HttpServletRequest)event.getSuppliedRequest();
|
||||
final HttpServletResponse response = (HttpServletResponse)event.getSuppliedResponse();
|
||||
final HttpServletRequest request = Request.unwrap(event.getSuppliedRequest());
|
||||
final HttpServletResponse response = Response.unwrap(event.getSuppliedResponse());
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} {} {} on {}", request.getDispatcherType(), request.getMethod(), target, channel);
|
||||
|
|
|
@ -136,6 +136,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
|
||||
public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
|
||||
|
||||
public static final String MAX_FORM_KEYS_KEY = "org.eclipse.jetty.server.Request.maxFormKeys";
|
||||
public static final String MAX_FORM_CONTENT_SIZE_KEY = "org.eclipse.jetty.server.Request.maxFormContentSize";
|
||||
public static final int DEFAULT_MAX_FORM_KEYS = 1000;
|
||||
public static final int DEFAULT_MAX_FORM_CONTENT_SIZE = 200000;
|
||||
|
||||
/**
|
||||
* Get the current ServletContext implementation.
|
||||
*
|
||||
|
@ -188,8 +193,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
|
||||
private Logger _logger;
|
||||
private boolean _allowNullPathInfo;
|
||||
private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys", -1).intValue();
|
||||
private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize", -1).intValue();
|
||||
private int _maxFormKeys = Integer.getInteger(MAX_FORM_KEYS_KEY, DEFAULT_MAX_FORM_KEYS);
|
||||
private int _maxFormContentSize = Integer.getInteger(MAX_FORM_CONTENT_SIZE_KEY, DEFAULT_MAX_FORM_CONTENT_SIZE);
|
||||
private boolean _compactPath = false;
|
||||
private boolean _usingSecurityManager = System.getSecurityManager() != null;
|
||||
|
||||
|
@ -784,9 +789,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
throw new IllegalStateException("Null contextPath");
|
||||
|
||||
if (_logger == null)
|
||||
{
|
||||
_logger = Log.getLogger(ContextHandler.class.getName() + getLogNameSuffix());
|
||||
}
|
||||
|
||||
ClassLoader oldClassloader = null;
|
||||
Thread currentThread = null;
|
||||
|
|
|
@ -139,13 +139,10 @@ public class ResponseTest
|
|||
private Throwable _channelError;
|
||||
|
||||
@Override
|
||||
public void send(MetaData.Response info, boolean head, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
public void send(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
if (BufferUtil.hasContent(content))
|
||||
{
|
||||
BufferUtil.append(_content, content);
|
||||
}
|
||||
|
||||
if (_channelError == null)
|
||||
callback.succeeded();
|
||||
else
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
public class ServletRequestWrapperTest
|
||||
{
|
||||
private Server server;
|
||||
private LocalConnector connector;
|
||||
private RequestHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
public void init() throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new LocalConnector(server, new HttpConnectionFactory());
|
||||
server.addConnector(connector);
|
||||
|
||||
handler = new RequestHandler();
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServletRequestWrapper() throws Exception
|
||||
{
|
||||
String request = "GET / HTTP/1.1\r\n" +
|
||||
"Host: whatever\r\n" +
|
||||
"\n";
|
||||
|
||||
String response = connector.getResponse(request);
|
||||
assertThat("Response", response, containsString("200"));
|
||||
}
|
||||
|
||||
private class RequestWrapper extends ServletRequestWrapper
|
||||
{
|
||||
public RequestWrapper(ServletRequest request)
|
||||
{
|
||||
super(request);
|
||||
}
|
||||
}
|
||||
|
||||
private class RequestHandler extends AbstractHandler
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
RequestWrapper requestWrapper = new RequestWrapper(request);
|
||||
AsyncContext context = request.startAsync(requestWrapper, response);
|
||||
context.complete();
|
||||
baseRequest.setHandled(true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2019 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.servlet;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Objects;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextAttributeEvent;
|
||||
import javax.servlet.ServletContextAttributeListener;
|
||||
|
||||
import org.eclipse.jetty.util.Decorator;
|
||||
|
||||
/**
|
||||
* A ServletContextAttributeListener that listens for a context
|
||||
* attribute to obtain a decorator instance. The instance is then either
|
||||
* coerced to a {@link Decorator} or reflected for decorator compatible methods
|
||||
* so it can be added to the {@link ServletContextHandler#getObjectFactory()} as a
|
||||
* {@link Decorator}.
|
||||
*/
|
||||
public class DecoratingListener implements ServletContextAttributeListener
|
||||
{
|
||||
private static final MethodType DECORATE_TYPE;
|
||||
private static final MethodType DESTROY_TYPE;
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
DECORATE_TYPE = MethodType.methodType(Object.class, Object.class);
|
||||
DESTROY_TYPE = MethodType.methodType(void.class, Object.class);
|
||||
|
||||
// Check we have the right MethodTypes for the current Decorator signatures
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
lookup.findVirtual(Decorator.class, "decorate", DECORATE_TYPE);
|
||||
lookup.findVirtual(Decorator.class, "destroy", DESTROY_TYPE);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final ServletContextHandler _context;
|
||||
private final String _attributeName;
|
||||
private Decorator _decorator;
|
||||
|
||||
public DecoratingListener(ServletContextHandler context, String attributeName)
|
||||
{
|
||||
Objects.requireNonNull(context);
|
||||
Objects.requireNonNull(attributeName);
|
||||
_context = context;
|
||||
_attributeName = attributeName;
|
||||
Object decorator = _context.getAttribute(_attributeName);
|
||||
if (decorator != null)
|
||||
_context.getObjectFactory().addDecorator(asDecorator(decorator));
|
||||
}
|
||||
|
||||
public String getAttributeName()
|
||||
{
|
||||
return _attributeName;
|
||||
}
|
||||
|
||||
public ServletContext getServletContext()
|
||||
{
|
||||
return _context.getServletContext();
|
||||
}
|
||||
|
||||
private Decorator asDecorator(Object object)
|
||||
{
|
||||
if (object == null)
|
||||
return null;
|
||||
if (object instanceof Decorator)
|
||||
return (Decorator)object;
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> clazz = object.getClass();
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
MethodHandle decorate = lookup.findVirtual(clazz, "decorate", DECORATE_TYPE);
|
||||
MethodHandle destroy = lookup.findVirtual(clazz, "destroy", DESTROY_TYPE);
|
||||
return new DynamicDecorator(object, decorate, destroy);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeAdded(ServletContextAttributeEvent event)
|
||||
{
|
||||
if (_attributeName.equals(event.getName()))
|
||||
{
|
||||
_decorator = asDecorator(event.getValue());
|
||||
_context.getObjectFactory().addDecorator(_decorator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeRemoved(ServletContextAttributeEvent event)
|
||||
{
|
||||
if (_attributeName.equals(event.getName()) && _decorator != null)
|
||||
{
|
||||
_context.getObjectFactory().removeDecorator(_decorator);
|
||||
_decorator = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attributeReplaced(ServletContextAttributeEvent event)
|
||||
{
|
||||
attributeRemoved(event);
|
||||
attributeAdded(event);
|
||||
}
|
||||
|
||||
private static class DynamicDecorator implements Decorator
|
||||
{
|
||||
private final Object _object;
|
||||
private final MethodHandle _decorate;
|
||||
private final MethodHandle _destroy;
|
||||
|
||||
private DynamicDecorator(Object object, MethodHandle decorate, MethodHandle destroy)
|
||||
{
|
||||
_object = object;
|
||||
_decorate = decorate;
|
||||
_destroy = destroy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T decorate(T o)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T)_decorate.invoke(_object, o);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(Object o)
|
||||
{
|
||||
try
|
||||
{
|
||||
_destroy.invoke(_object, o);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue