Merged branch 'jetty-10.0.x' into 'jetty-10.0.x-3952-server_direct_heap_bytebuffers'.

This commit is contained in:
Simone Bordet 2019-08-14 15:45:49 +02:00
commit 985d98296b
144 changed files with 4763 additions and 1558 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &lt;T&gt; T decorate(T o)
* {
* BeanManager manager = CDI.current().getBeanManager();
* manager.createInjectionTarget(manager.createAnnotatedType((Class&lt;T&gt;)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);
}
}
}

View File

@ -0,0 +1 @@
org.eclipse.jetty.cdi.CdiServletContainerInitializer

View File

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

View File

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

View File

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

View File

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

View File

@ -609,7 +609,7 @@ public abstract class HttpReceiver
}
@Override
protected Action process() throws Throwable
protected Action process()
{
while (true)
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -116,7 +116,7 @@ public class HttpClientCustomProxyTest
{
private CAFEBABEProxy(Origin.Address address, boolean secure)
{
super(address, secure);
super(address, secure, null);
}
@Override

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -133,6 +133,7 @@ public enum HttpHeader
C_AUTHORITY(":authority"),
C_PATH(":path"),
C_STATUS(":status"),
C_PROTOCOL(":protocol"),
UNKNOWN("::UNKNOWN::");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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