Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.
This commit is contained in:
commit
b1e2f80017
130
NOTICE.txt
130
NOTICE.txt
|
@ -1,41 +1,42 @@
|
|||
==============================================================
|
||||
Jetty Web Container
|
||||
Copyright 1995-2019 Mort Bay Consulting Pty Ltd.
|
||||
==============================================================
|
||||
Notices for Eclipse Jetty
|
||||
=========================
|
||||
This content is produced and maintained by the Eclipse Jetty project.
|
||||
|
||||
The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd
|
||||
unless otherwise noted.
|
||||
Project home: https://www.eclipse.org/jetty/
|
||||
|
||||
Jetty is dual licensed under both
|
||||
Trademarks
|
||||
----------
|
||||
Eclipse Jetty, and Jetty are trademarks of the Eclipse Foundation.
|
||||
|
||||
* The Apache 2.0 License
|
||||
http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
Copyright
|
||||
---------
|
||||
All contributions are the property of the respective authors or of
|
||||
entities to which copyright has been assigned by the authors (eg. employer).
|
||||
|
||||
and
|
||||
Declared Project Licenses
|
||||
-------------------------
|
||||
This artifacts of this project are made available under the terms of:
|
||||
|
||||
* The Eclipse Public 1.0 License
|
||||
* the Eclipse Public License v. 1.0
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
SPDX-License-Identifier: EPL-1.0
|
||||
|
||||
Jetty may be distributed under either license.
|
||||
or
|
||||
|
||||
------
|
||||
Eclipse
|
||||
* the Apache License, Version 2.0
|
||||
https://www.apache.org/licenses/LICENSE-2.0.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
The following artifacts are EPL.
|
||||
The following dependencies are EPL.
|
||||
* org.eclipse.jetty.orbit:org.eclipse.jdt.core
|
||||
|
||||
The following artifacts are EPL and ASL2.
|
||||
The following dependencies are EPL and ASL2.
|
||||
* org.eclipse.jetty.orbit:javax.security.auth.message
|
||||
|
||||
|
||||
The following artifacts are EPL and CDDL 1.0.
|
||||
The following dependencies are EPL and CDDL 1.0.
|
||||
* org.eclipse.jetty.orbit:javax.mail.glassfish
|
||||
|
||||
|
||||
------
|
||||
Oracle
|
||||
|
||||
The following artifacts are CDDL + GPLv2 with classpath exception.
|
||||
The following dependencies are CDDL + GPLv2 with classpath exception.
|
||||
https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html
|
||||
|
||||
* javax.servlet:javax.servlet-api
|
||||
|
@ -43,72 +44,55 @@ https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html
|
|||
* javax.transaction:javax.transaction-api
|
||||
* javax.websocket:javax.websocket-api
|
||||
|
||||
------
|
||||
Oracle OpenJDK
|
||||
|
||||
If ALPN is used to negotiate HTTP/2 connections, then the following
|
||||
artifacts may be included in the distribution or downloaded when ALPN
|
||||
module is selected.
|
||||
distribution may be included in the distribution or downloaded when ALPN
|
||||
module is selected. These artifacts replace/modify OpenJDK classes.
|
||||
The modifications are hosted at github and both modified and original
|
||||
are under GPL v2 with classpath exceptions.
|
||||
http://openjdk.java.net/legal/gplv2+ce.html
|
||||
|
||||
* java.sun.security.ssl
|
||||
|
||||
These artifacts replace/modify OpenJDK classes. The modififications
|
||||
are hosted at github and both modified and original are under GPL v2 with
|
||||
classpath exceptions.
|
||||
http://openjdk.java.net/legal/gplv2+ce.html
|
||||
|
||||
|
||||
------
|
||||
OW2
|
||||
|
||||
The following artifacts are licensed by the OW2 Foundation according to the
|
||||
The following dependencies are licensed by the OW2 Foundation according to the
|
||||
terms of http://asm.ow2.org/license.html
|
||||
|
||||
org.ow2.asm:asm-commons
|
||||
org.ow2.asm:asm
|
||||
* org.ow2.asm:asm-commons
|
||||
* org.ow2.asm:asm
|
||||
|
||||
The following dependencies are ASL2 licensed.
|
||||
|
||||
------
|
||||
Apache
|
||||
* org.apache.taglibs:taglibs-standard-spec
|
||||
* org.apache.taglibs:taglibs-standard-impl
|
||||
|
||||
The following artifacts are ASL2 licensed.
|
||||
|
||||
org.apache.taglibs:taglibs-standard-spec
|
||||
org.apache.taglibs:taglibs-standard-impl
|
||||
|
||||
|
||||
------
|
||||
MortBay
|
||||
|
||||
The following artifacts are ASL2 licensed. Based on selected classes from
|
||||
The following dependencies are ASL2 licensed. Based on selected classes from
|
||||
following Apache Tomcat jars, all ASL2 licensed.
|
||||
|
||||
org.mortbay.jasper:apache-jsp
|
||||
org.apache.tomcat:tomcat-jasper
|
||||
org.apache.tomcat:tomcat-juli
|
||||
org.apache.tomcat:tomcat-jsp-api
|
||||
org.apache.tomcat:tomcat-el-api
|
||||
org.apache.tomcat:tomcat-jasper-el
|
||||
org.apache.tomcat:tomcat-api
|
||||
org.apache.tomcat:tomcat-util-scan
|
||||
org.apache.tomcat:tomcat-util
|
||||
|
||||
org.mortbay.jasper:apache-el
|
||||
org.apache.tomcat:tomcat-jasper-el
|
||||
org.apache.tomcat:tomcat-el-api
|
||||
|
||||
|
||||
------
|
||||
Mortbay
|
||||
* org.mortbay.jasper:apache-jsp
|
||||
* org.apache.tomcat:tomcat-jasper
|
||||
* org.apache.tomcat:tomcat-juli
|
||||
* org.apache.tomcat:tomcat-jsp-api
|
||||
* org.apache.tomcat:tomcat-el-api
|
||||
* org.apache.tomcat:tomcat-jasper-el
|
||||
* org.apache.tomcat:tomcat-api
|
||||
* org.apache.tomcat:tomcat-util-scan
|
||||
* org.apache.tomcat:tomcat-util
|
||||
* org.mortbay.jasper:apache-el
|
||||
* org.apache.tomcat:tomcat-jasper-el
|
||||
* org.apache.tomcat:tomcat-el-api
|
||||
|
||||
The following artifacts are CDDL + GPLv2 with classpath exception.
|
||||
|
||||
https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html
|
||||
|
||||
org.eclipse.jetty.toolchain:jetty-servlet-api
|
||||
* org.eclipse.jetty.toolchain:jetty-schemas
|
||||
|
||||
------
|
||||
Assorted
|
||||
Cryptography
|
||||
------------
|
||||
Content may contain encryption software. The country in which you are currently
|
||||
may have restrictions on the import, possession, and use, and/or re-export to
|
||||
another country, of encryption software. BEFORE using any encryption software,
|
||||
please check the country's laws, regulations and policies concerning the import,
|
||||
possession, or use, and re-export of encryption software, to see if this is
|
||||
permitted.
|
||||
|
||||
The UnixCrypt.java code implements the one way cryptography used by
|
||||
Unix systems for simple password protection. Copyright 1996 Aki Yoshida,
|
||||
|
|
|
@ -27,11 +27,6 @@ public class DuplexHttpDestination extends HttpDestination
|
|||
{
|
||||
public DuplexHttpDestination(HttpClient client, Origin origin)
|
||||
{
|
||||
this(client, new Key(origin, null));
|
||||
}
|
||||
|
||||
public DuplexHttpDestination(HttpClient client, Key key)
|
||||
{
|
||||
super(client, key);
|
||||
super(client, origin);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
public static final String USER_AGENT = "Jetty/" + Jetty.VERSION;
|
||||
private static final Logger LOG = Log.getLogger(HttpClient.class);
|
||||
|
||||
private final ConcurrentMap<HttpDestination.Key, HttpDestination> destinations = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<Origin, HttpDestination> destinations = new ConcurrentHashMap<>();
|
||||
private final ProtocolHandlers handlers = new ProtocolHandlers();
|
||||
private final List<Request.Listener> requestListeners = new ArrayList<>();
|
||||
private final Set<ContentDecoder.Factory> decoderFactories = new ContentDecoderFactorySet();
|
||||
|
@ -499,33 +499,33 @@ public class HttpClient extends ContainerLifeCycle
|
|||
|
||||
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);
|
||||
Origin origin = transport.newOrigin((HttpRequest)request);
|
||||
HttpDestination destination = resolveDestination(origin);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Resolved {} for {}", destination, request);
|
||||
return destination;
|
||||
}
|
||||
|
||||
private Origin createOrigin(String scheme, String host, int port)
|
||||
public Origin createOrigin(HttpRequest request, Origin.Protocol protocol)
|
||||
{
|
||||
String scheme = request.getScheme();
|
||||
if (!HttpScheme.HTTP.is(scheme) && !HttpScheme.HTTPS.is(scheme) &&
|
||||
!HttpScheme.WS.is(scheme) && !HttpScheme.WSS.is(scheme))
|
||||
throw new IllegalArgumentException("Invalid protocol " + scheme);
|
||||
|
||||
scheme = scheme.toLowerCase(Locale.ENGLISH);
|
||||
String host = request.getHost();
|
||||
host = host.toLowerCase(Locale.ENGLISH);
|
||||
int port = request.getPort();
|
||||
port = normalizePort(scheme, port);
|
||||
|
||||
return new Origin(scheme, host, port);
|
||||
return new Origin(scheme, host, port, request.getTag(), protocol);
|
||||
}
|
||||
|
||||
HttpDestination resolveDestination(HttpDestination.Key key)
|
||||
HttpDestination resolveDestination(Origin origin)
|
||||
{
|
||||
return destinations.computeIfAbsent(key, k ->
|
||||
return destinations.computeIfAbsent(origin, o ->
|
||||
{
|
||||
HttpDestination destination = getTransport().newHttpDestination(k);
|
||||
HttpDestination destination = getTransport().newHttpDestination(o);
|
||||
// Start the destination before it's published to other threads.
|
||||
addManaged(destination);
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -537,7 +537,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
protected boolean removeDestination(HttpDestination destination)
|
||||
{
|
||||
removeBean(destination);
|
||||
return destinations.remove(destination.getKey(), destination);
|
||||
return destinations.remove(destination.getOrigin(), destination);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -51,13 +51,12 @@ public interface HttpClientTransport extends ClientConnectionFactory
|
|||
public void setHttpClient(HttpClient client);
|
||||
|
||||
/**
|
||||
* Creates a new Key with the given request and origin.
|
||||
* Creates a new Origin with the given request.
|
||||
*
|
||||
* @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
|
||||
* @param request the request that triggers the creation of the Origin
|
||||
* @return an Origin that identifies a destination
|
||||
*/
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin);
|
||||
public Origin newOrigin(HttpRequest request);
|
||||
|
||||
/**
|
||||
* Creates a new, transport-specific, {@link HttpDestination} object.
|
||||
|
@ -65,10 +64,10 @@ public interface HttpClientTransport extends ClientConnectionFactory
|
|||
* {@link HttpDestination} controls the destination-connection cardinality: protocols like
|
||||
* HTTP have 1-N cardinality, while multiplexed protocols like HTTP/2 have a 1-1 cardinality.
|
||||
*
|
||||
* @param key the destination key
|
||||
* @param origin the destination origin
|
||||
* @return a new, transport-specific, {@link HttpDestination} object
|
||||
*/
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key);
|
||||
public HttpDestination newHttpDestination(Origin origin);
|
||||
|
||||
/**
|
||||
* Establishes a physical connection to the given {@code address}.
|
||||
|
|
|
@ -23,25 +23,20 @@ import java.io.IOException;
|
|||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
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.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.CyclicTimeout;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
|
@ -58,12 +53,12 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
import org.eclipse.jetty.util.thread.Sweeper;
|
||||
|
||||
@ManagedObject
|
||||
public class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable
|
||||
public abstract class HttpDestination extends ContainerLifeCycle implements Destination, Closeable, Callback, Dumpable
|
||||
{
|
||||
protected static final Logger LOG = Log.getLogger(HttpDestination.class);
|
||||
|
||||
private final HttpClient client;
|
||||
private final Key key;
|
||||
private final Origin origin;
|
||||
private final Queue<HttpExchange> exchanges;
|
||||
private final RequestNotifier requestNotifier;
|
||||
private final ResponseNotifier responseNotifier;
|
||||
|
@ -73,15 +68,10 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
|
|||
private final TimeoutTask timeout;
|
||||
private ConnectionPool connectionPool;
|
||||
|
||||
public HttpDestination(HttpClient client, Key key)
|
||||
{
|
||||
this(client, key, Function.identity());
|
||||
}
|
||||
|
||||
public HttpDestination(HttpClient client, Key key, Function<ClientConnectionFactory, ClientConnectionFactory> factoryFn)
|
||||
public HttpDestination(HttpClient client, Origin origin)
|
||||
{
|
||||
this.client = client;
|
||||
this.key = key;
|
||||
this.origin = origin;
|
||||
|
||||
this.exchanges = newExchangeQueue(client);
|
||||
|
||||
|
@ -90,21 +80,9 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
|
|||
|
||||
this.timeout = new TimeoutTask(client.getScheduler());
|
||||
|
||||
String host = HostPort.normalizeHost(getHost());
|
||||
if (!client.isDefaultPort(getScheme(), getPort()))
|
||||
host += ":" + getPort();
|
||||
hostField = new HttpField(HttpHeader.HOST, host);
|
||||
|
||||
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
|
||||
this.proxy = proxyConfig.match(getOrigin());
|
||||
|
||||
this.connectionFactory = factoryFn.apply(createClientConnectionFactory());
|
||||
}
|
||||
|
||||
private ClientConnectionFactory createClientConnectionFactory()
|
||||
{
|
||||
ProxyConfiguration.Proxy proxy = getProxy();
|
||||
ClientConnectionFactory connectionFactory = getHttpClient().getTransport();
|
||||
proxy = proxyConfig.match(origin);
|
||||
ClientConnectionFactory connectionFactory = client.getTransport();
|
||||
if (proxy != null)
|
||||
{
|
||||
connectionFactory = proxy.newClientConnectionFactory(connectionFactory);
|
||||
|
@ -116,7 +94,15 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
|
|||
if (isSecure())
|
||||
connectionFactory = newSslClientConnectionFactory(null, connectionFactory);
|
||||
}
|
||||
return connectionFactory;
|
||||
Object tag = origin.getTag();
|
||||
if (tag instanceof ClientConnectionFactory.Decorator)
|
||||
connectionFactory = ((ClientConnectionFactory.Decorator)tag).apply(connectionFactory);
|
||||
this.connectionFactory = connectionFactory;
|
||||
|
||||
String host = HostPort.normalizeHost(getHost());
|
||||
if (!client.isDefaultPort(getScheme(), getPort()))
|
||||
host += ":" + getPort();
|
||||
hostField = new HttpField(HttpHeader.HOST, host);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -165,14 +151,9 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
|
|||
return client;
|
||||
}
|
||||
|
||||
public Key getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
public Origin getOrigin()
|
||||
{
|
||||
return key.origin;
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Queue<HttpExchange> getHttpExchanges()
|
||||
|
@ -499,7 +480,7 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
|
|||
|
||||
public String asString()
|
||||
{
|
||||
return getKey().asString();
|
||||
return getOrigin().asString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -520,166 +501,6 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
|
|||
void setMaxRequestsPerConnection(int maxRequestsPerConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Class that groups the elements that uniquely identify a destination.</p>
|
||||
* <p>The elements are an {@link Origin}, a {@link Protocol} and an opaque
|
||||
* string that further distinguishes destinations that have the same origin
|
||||
* and protocol.</p>
|
||||
* <p>In general it is possible that, for the same origin, the server can
|
||||
* speak different protocols (for example, clear-text HTTP/1.1 and clear-text
|
||||
* HTTP/2), so the {@link Protocol} makes that distinction.</p>
|
||||
* <p>Furthermore, it may be desirable to have different destinations for
|
||||
* the same origin and protocol (for example, when using the PROXY protocol
|
||||
* in a reverse proxy server, you want to be able to map the client ip:port
|
||||
* to the destination {@code kind}, so that all the connections to the server
|
||||
* associated to that destination can specify the PROXY protocol bytes for
|
||||
* that particular client connection.</p>
|
||||
*/
|
||||
public static class Key
|
||||
{
|
||||
private final Origin origin;
|
||||
private final Protocol protocol;
|
||||
private final String kind;
|
||||
|
||||
/**
|
||||
* Creates a Key with the given origin and protocol and a {@code null} kind.
|
||||
*
|
||||
* @param origin the origin
|
||||
* @param protocol the protocol
|
||||
*/
|
||||
public Key(Origin origin, Protocol protocol)
|
||||
{
|
||||
this(origin, protocol, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Key with the given origin and protocol and kind.
|
||||
*
|
||||
* @param origin the origin
|
||||
* @param protocol the protocol
|
||||
* @param kind the opaque kind
|
||||
*/
|
||||
public Key(Origin origin, Protocol protocol, String kind)
|
||||
{
|
||||
this.origin = origin;
|
||||
this.protocol = protocol;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public Origin getOrigin()
|
||||
{
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getKind()
|
||||
{
|
||||
return kind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Key that = (Key)obj;
|
||||
return origin.equals(that.origin) &&
|
||||
Objects.equals(protocol, that.protocol) &&
|
||||
Objects.equals(kind, that.kind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(origin, protocol, kind);
|
||||
}
|
||||
|
||||
public String asString()
|
||||
{
|
||||
return String.format("%s|%s,kind=%s",
|
||||
origin.asString(),
|
||||
protocol == null ? "null" : protocol.asString(),
|
||||
kind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The representation of a network protocol.</p>
|
||||
* <p>A network protocol may have multiple protocol <em>names</em>
|
||||
* associated to it, for example {@code ["h2", "h2-17", "h2-16"]}.</p>
|
||||
* <p>A Protocol is then rendered into a {@link ClientConnectionFactory}
|
||||
* chain, for example in
|
||||
* {@link HttpClientTransportDynamic#newConnection(EndPoint, Map)}.</p>
|
||||
*/
|
||||
public static class Protocol
|
||||
{
|
||||
private final List<String> protocols;
|
||||
private final boolean negotiate;
|
||||
|
||||
/**
|
||||
* Creates a Protocol with the given list of protocol names
|
||||
* and whether it should negotiate the protocol.
|
||||
*
|
||||
* @param protocols the protocol names
|
||||
* @param negotiate whether the protocol should be negotiated
|
||||
*/
|
||||
public Protocol(List<String> protocols, boolean negotiate)
|
||||
{
|
||||
this.protocols = protocols;
|
||||
this.negotiate = negotiate;
|
||||
}
|
||||
|
||||
public List<String> getProtocols()
|
||||
{
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public boolean isNegotiate()
|
||||
{
|
||||
return negotiate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Protocol that = (Protocol)obj;
|
||||
return protocols.equals(that.protocols) && negotiate == that.negotiate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(protocols, negotiate);
|
||||
}
|
||||
|
||||
public String asString()
|
||||
{
|
||||
return String.format("proto=%s,nego=%b", protocols, negotiate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class enforces the total timeout for exchanges that are still in the queue.
|
||||
* The total timeout for exchanges that are not in the destination queue is enforced
|
||||
|
|
|
@ -53,25 +53,25 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
|
||||
public HttpProxy(Origin.Address address, boolean secure)
|
||||
{
|
||||
this(address, secure, null, new HttpDestination.Protocol(List.of("http/1.1"), false));
|
||||
this(address, secure, null, new Origin.Protocol(List.of("http/1.1"), false));
|
||||
}
|
||||
|
||||
public HttpProxy(Origin.Address address, boolean secure, HttpDestination.Protocol protocol)
|
||||
public HttpProxy(Origin.Address address, boolean secure, Origin.Protocol protocol)
|
||||
{
|
||||
this(address, secure, null, Objects.requireNonNull(protocol));
|
||||
}
|
||||
|
||||
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory)
|
||||
{
|
||||
this(address, true, sslContextFactory, new HttpDestination.Protocol(List.of("http/1.1"), false));
|
||||
this(address, true, sslContextFactory, new Origin.Protocol(List.of("http/1.1"), false));
|
||||
}
|
||||
|
||||
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory, HttpDestination.Protocol protocol)
|
||||
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
|
||||
{
|
||||
this(address, true, sslContextFactory, Objects.requireNonNull(protocol));
|
||||
}
|
||||
|
||||
private HttpProxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory, HttpDestination.Protocol protocol)
|
||||
private HttpProxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
|
||||
{
|
||||
super(address, secure, sslContextFactory, Objects.requireNonNull(protocol));
|
||||
}
|
||||
|
@ -85,13 +85,7 @@ 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 new Origin(scheme, getAddress());
|
||||
return URI.create(getOrigin().asString());
|
||||
}
|
||||
|
||||
private class HttpProxyClientConnectionFactory implements ClientConnectionFactory
|
||||
|
@ -107,7 +101,7 @@ 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);
|
||||
HttpDestination.Protocol serverProtocol = destination.getKey().getProtocol();
|
||||
Origin.Protocol serverProtocol = destination.getOrigin().getProtocol();
|
||||
boolean sameProtocol = proxySpeaksServerProtocol(serverProtocol);
|
||||
if (destination.isSecure() || !sameProtocol)
|
||||
{
|
||||
|
@ -135,7 +129,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
}
|
||||
}
|
||||
|
||||
private boolean proxySpeaksServerProtocol(HttpDestination.Protocol serverProtocol)
|
||||
private boolean proxySpeaksServerProtocol(Origin.Protocol serverProtocol)
|
||||
{
|
||||
return serverProtocol != null && getProtocol().getProtocols().stream().anyMatch(p -> serverProtocol.getProtocols().stream().anyMatch(p::equalsIgnoreCase));
|
||||
}
|
||||
|
@ -151,7 +145,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
// 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()));
|
||||
HttpDestination proxyDestination = client.resolveDestination(getOrigin());
|
||||
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, proxyDestination);
|
||||
try
|
||||
{
|
||||
|
@ -170,7 +164,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
* tunnel after the TCP connection is succeeded, and needs to notify
|
||||
* the nested promise when the tunnel is established (or failed).</p>
|
||||
*/
|
||||
private class CreateTunnelPromise implements Promise<Connection>
|
||||
private static class CreateTunnelPromise implements Promise<Connection>
|
||||
{
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private final EndPoint endPoint;
|
||||
|
@ -283,7 +277,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
}
|
||||
}
|
||||
|
||||
private class ProxyConnection implements Connection
|
||||
private static class ProxyConnection implements Connection
|
||||
{
|
||||
private final Destination destination;
|
||||
private final Connection connection;
|
||||
|
@ -322,7 +316,7 @@ public class HttpProxy extends ProxyConfiguration.Proxy
|
|||
}
|
||||
}
|
||||
|
||||
private class TunnelPromise implements Promise<Connection>
|
||||
private static class TunnelPromise implements Promise<Connection>
|
||||
{
|
||||
private final Request request;
|
||||
private final Response.CompleteListener listener;
|
||||
|
|
|
@ -89,6 +89,7 @@ public class HttpRequest implements Request
|
|||
private BiFunction<Request, Request, Response.CompleteListener> pushListener;
|
||||
private Supplier<HttpFields> trailers;
|
||||
private String upgradeProtocol;
|
||||
private Object tag;
|
||||
|
||||
protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
|
||||
{
|
||||
|
@ -321,6 +322,19 @@ public class HttpRequest implements Request
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request tag(Object tag)
|
||||
{
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTag()
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request attribute(String name, Object value)
|
||||
{
|
||||
|
|
|
@ -34,14 +34,9 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
|||
*/
|
||||
public class MultiplexHttpDestination extends HttpDestination implements HttpDestination.Multiplexed
|
||||
{
|
||||
public MultiplexHttpDestination(HttpClient client, Key key)
|
||||
public MultiplexHttpDestination(HttpClient client, Origin origin)
|
||||
{
|
||||
this(client, key, Function.identity());
|
||||
}
|
||||
|
||||
public MultiplexHttpDestination(HttpClient client, Key key, Function<ClientConnectionFactory, ClientConnectionFactory> factoryFn)
|
||||
{
|
||||
super(client, key, factoryFn);
|
||||
super(client, origin);
|
||||
}
|
||||
|
||||
@ManagedAttribute(value = "The maximum number of concurrent requests per connection")
|
||||
|
|
|
@ -18,24 +18,68 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
|
||||
/**
|
||||
* <p>Class that groups the elements that uniquely identify a destination.</p>
|
||||
* <p>The elements are {@code scheme}, {@code host}, {@code port}, a
|
||||
* {@link Origin.Protocol} and a tag object that further distinguishes
|
||||
* destinations that have the same origin and protocol.</p>
|
||||
* <p>In general it is possible that, for the same origin, the server can
|
||||
* speak different protocols (for example, clear-text HTTP/1.1 and clear-text
|
||||
* HTTP/2), so the {@link Origin.Protocol} makes that distinction.</p>
|
||||
* <p>Furthermore, it may be desirable to have different destinations for
|
||||
* the same origin and protocol (for example, when using the PROXY protocol
|
||||
* in a reverse proxy server, you want to be able to map the client ip:port
|
||||
* to the destination {@code tag}, so that all the connections to the server
|
||||
* associated to that destination can specify the PROXY protocol bytes for
|
||||
* that particular client connection.</p>
|
||||
*/
|
||||
public class Origin
|
||||
{
|
||||
private final String scheme;
|
||||
private final Address address;
|
||||
private final Object tag;
|
||||
private final Protocol protocol;
|
||||
|
||||
public Origin(String scheme, String host, int port)
|
||||
{
|
||||
this(scheme, new Address(host, port));
|
||||
this(scheme, host, port, null);
|
||||
}
|
||||
|
||||
public Origin(String scheme, String host, int port, Object tag)
|
||||
{
|
||||
this(scheme, new Address(host, port), tag);
|
||||
}
|
||||
|
||||
public Origin(String scheme, String host, int port, Object tag, Protocol protocol)
|
||||
{
|
||||
this(scheme, new Address(host, port), tag, protocol);
|
||||
}
|
||||
|
||||
public Origin(String scheme, Address address)
|
||||
{
|
||||
this(scheme, address, null);
|
||||
}
|
||||
|
||||
public Origin(String scheme, Address address, Object tag)
|
||||
{
|
||||
this(scheme, address, tag, null);
|
||||
}
|
||||
|
||||
public Origin(String scheme, Address address, Object tag, Protocol protocol)
|
||||
{
|
||||
this.scheme = Objects.requireNonNull(scheme);
|
||||
this.address = address;
|
||||
this.tag = tag;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public String getScheme()
|
||||
|
@ -48,6 +92,16 @@ public class Origin
|
|||
return address;
|
||||
}
|
||||
|
||||
public Object getTag()
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
|
@ -56,13 +110,16 @@ public class Origin
|
|||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Origin that = (Origin)obj;
|
||||
return scheme.equals(that.scheme) && address.equals(that.address);
|
||||
return scheme.equals(that.scheme) &&
|
||||
address.equals(that.address) &&
|
||||
Objects.equals(tag, that.tag) &&
|
||||
Objects.equals(protocol, that.protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(scheme, address);
|
||||
return Objects.hash(scheme, address, tag, protocol);
|
||||
}
|
||||
|
||||
public String asString()
|
||||
|
@ -75,7 +132,12 @@ public class Origin
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString());
|
||||
return String.format("%s@%x[%s,tag=%s,protocol=%s]",
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
asString(),
|
||||
getTag(),
|
||||
getProtocol());
|
||||
}
|
||||
|
||||
public static class Address
|
||||
|
@ -127,4 +189,69 @@ public class Origin
|
|||
return asString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>The representation of a network protocol.</p>
|
||||
* <p>A network protocol may have multiple protocol <em>names</em>
|
||||
* associated to it, for example {@code ["h2", "h2-17", "h2-16"]}.</p>
|
||||
* <p>A Protocol is then rendered into a {@link ClientConnectionFactory}
|
||||
* chain, for example in
|
||||
* {@link HttpClientTransportDynamic#newConnection(EndPoint, Map)}.</p>
|
||||
*/
|
||||
public static class Protocol
|
||||
{
|
||||
private final List<String> protocols;
|
||||
private final boolean negotiate;
|
||||
|
||||
/**
|
||||
* Creates a Protocol with the given list of protocol names
|
||||
* and whether it should negotiate the protocol.
|
||||
*
|
||||
* @param protocols the protocol names
|
||||
* @param negotiate whether the protocol should be negotiated
|
||||
*/
|
||||
public Protocol(List<String> protocols, boolean negotiate)
|
||||
{
|
||||
this.protocols = protocols;
|
||||
this.negotiate = negotiate;
|
||||
}
|
||||
|
||||
public List<String> getProtocols()
|
||||
{
|
||||
return protocols;
|
||||
}
|
||||
|
||||
public boolean isNegotiate()
|
||||
{
|
||||
return negotiate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Protocol that = (Protocol)obj;
|
||||
return protocols.equals(that.protocols) && negotiate == that.negotiate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(protocols, negotiate);
|
||||
}
|
||||
|
||||
public String asString()
|
||||
{
|
||||
return String.format("proto=%s,nego=%b", protocols, negotiate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x[%s]", getClass().getSimpleName(), hashCode(), asString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
@ -60,20 +61,26 @@ public class ProxyConfiguration
|
|||
|
||||
public abstract static class Proxy
|
||||
{
|
||||
// TO use IPAddress Map
|
||||
// TODO use InetAddressSet? Or IncludeExcludeSet?
|
||||
private final Set<String> included = new HashSet<>();
|
||||
private final Set<String> excluded = new HashSet<>();
|
||||
private final Origin.Address address;
|
||||
private final boolean secure;
|
||||
private final Origin origin;
|
||||
private final SslContextFactory.Client sslContextFactory;
|
||||
private final HttpDestination.Protocol protocol;
|
||||
|
||||
protected Proxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory, HttpDestination.Protocol protocol)
|
||||
protected Proxy(Origin.Address address, boolean secure, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
|
||||
{
|
||||
this.address = address;
|
||||
this.secure = secure;
|
||||
this(new Origin(secure ? HttpScheme.HTTPS.asString() : HttpScheme.HTTP.asString(), address, null, protocol), sslContextFactory);
|
||||
}
|
||||
|
||||
protected Proxy(Origin origin, SslContextFactory.Client sslContextFactory)
|
||||
{
|
||||
this.origin = origin;
|
||||
this.sslContextFactory = sslContextFactory;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public Origin getOrigin()
|
||||
{
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,7 +88,7 @@ public class ProxyConfiguration
|
|||
*/
|
||||
public Origin.Address getAddress()
|
||||
{
|
||||
return address;
|
||||
return origin.getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +96,7 @@ public class ProxyConfiguration
|
|||
*/
|
||||
public boolean isSecure()
|
||||
{
|
||||
return secure;
|
||||
return HttpScheme.HTTPS.is(origin.getScheme());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,9 +110,9 @@ public class ProxyConfiguration
|
|||
/**
|
||||
* @return the protocol spoken by this proxy
|
||||
*/
|
||||
public HttpDestination.Protocol getProtocol()
|
||||
public Origin.Protocol getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
return origin.getProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,14 +187,14 @@ public class ProxyConfiguration
|
|||
|
||||
/**
|
||||
* @param connectionFactory the nested {@link ClientConnectionFactory}
|
||||
* @return a new {@link ClientConnectionFactory} for this {@link Proxy}
|
||||
* @return a new {@link ClientConnectionFactory} for this Proxy
|
||||
*/
|
||||
public abstract ClientConnectionFactory newClientConnectionFactory(ClientConnectionFactory connectionFactory);
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return address.toString();
|
||||
return origin.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,621 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.client;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
/**
|
||||
* <p>ClientConnectionFactory for the
|
||||
* <a href="http://www.haproxy.org/download/2.1/doc/proxy-protocol.txt">PROXY protocol</a>.</p>
|
||||
* <p>Use the {@link V1} or {@link V2} versions of this class to specify what version of the
|
||||
* PROXY protocol you want to use.</p>
|
||||
*/
|
||||
public abstract class ProxyProtocolClientConnectionFactory implements ClientConnectionFactory
|
||||
{
|
||||
/**
|
||||
* A ClientConnectionFactory for the PROXY protocol version 1.
|
||||
*/
|
||||
public static class V1 extends ProxyProtocolClientConnectionFactory
|
||||
{
|
||||
public V1(ClientConnectionFactory factory)
|
||||
{
|
||||
super(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProxyProtocolConnection newProxyProtocolConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
Tag tag = (Tag)destination.getOrigin().getTag();
|
||||
if (tag == null)
|
||||
{
|
||||
InetSocketAddress local = endPoint.getLocalAddress();
|
||||
InetSocketAddress remote = endPoint.getRemoteAddress();
|
||||
boolean ipv4 = local.getAddress() instanceof Inet4Address;
|
||||
tag = new Tag(ipv4 ? "TCP4" : "TCP6", local.getAddress().getHostAddress(), local.getPort(), remote.getAddress().getHostAddress(), remote.getPort());
|
||||
}
|
||||
return new ProxyProtocolConnectionV1(endPoint, executor, getClientConnectionFactory(), context, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>PROXY protocol version 1 metadata holder to be used in conjunction
|
||||
* with {@link org.eclipse.jetty.client.api.Request#tag(Object)}.</p>
|
||||
* <p>Instances of this class are associated to a destination so that
|
||||
* all connections of that destination will initiate the communication
|
||||
* with the PROXY protocol version 1 bytes specified by this metadata.</p>
|
||||
*/
|
||||
public static class Tag implements ClientConnectionFactory.Decorator
|
||||
{
|
||||
/**
|
||||
* The PROXY V1 Tag typically used to "ping" the server.
|
||||
*/
|
||||
public static final Tag UNKNOWN = new Tag("UNKNOWN", null, 0, null, 0);
|
||||
|
||||
private final String family;
|
||||
private final String srcIP;
|
||||
private final int srcPort;
|
||||
private final String dstIP;
|
||||
private final int dstPort;
|
||||
|
||||
/**
|
||||
* <p>Creates a Tag whose metadata will be derived from the underlying EndPoint.</p>
|
||||
*/
|
||||
public Tag()
|
||||
{
|
||||
this(null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a Tag with the given source metadata.</p>
|
||||
* <p>The destination metadata will be derived from the underlying EndPoint.</p>
|
||||
*
|
||||
* @param srcIP the source IP address
|
||||
* @param srcPort the source port
|
||||
*/
|
||||
public Tag(String srcIP, int srcPort)
|
||||
{
|
||||
this(null, srcIP, srcPort, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a Tag with the given metadata.</p>
|
||||
*
|
||||
* @param family the protocol family
|
||||
* @param srcIP the source IP address
|
||||
* @param srcPort the source port
|
||||
* @param dstIP the destination IP address
|
||||
* @param dstPort the destination port
|
||||
*/
|
||||
public Tag(String family, String srcIP, int srcPort, String dstIP, int dstPort)
|
||||
{
|
||||
this.family = family;
|
||||
this.srcIP = srcIP;
|
||||
this.srcPort = srcPort;
|
||||
this.dstIP = dstIP;
|
||||
this.dstPort = dstPort;
|
||||
}
|
||||
|
||||
public String getFamily()
|
||||
{
|
||||
return family;
|
||||
}
|
||||
|
||||
public String getSourceAddress()
|
||||
{
|
||||
return srcIP;
|
||||
}
|
||||
|
||||
public int getSourcePort()
|
||||
{
|
||||
return srcPort;
|
||||
}
|
||||
|
||||
public String getDestinationAddress()
|
||||
{
|
||||
return dstIP;
|
||||
}
|
||||
|
||||
public int getDestinationPort()
|
||||
{
|
||||
return dstPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory apply(ClientConnectionFactory factory)
|
||||
{
|
||||
return new V1(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Tag that = (Tag)obj;
|
||||
return Objects.equals(family, that.family) &&
|
||||
Objects.equals(srcIP, that.srcIP) &&
|
||||
srcPort == that.srcPort &&
|
||||
Objects.equals(dstIP, that.dstIP) &&
|
||||
dstPort == that.dstPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(family, srcIP, srcPort, dstIP, dstPort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A ClientConnectionFactory for the PROXY protocol version 2.
|
||||
*/
|
||||
public static class V2 extends ProxyProtocolClientConnectionFactory
|
||||
{
|
||||
public V2(ClientConnectionFactory factory)
|
||||
{
|
||||
super(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProxyProtocolConnection newProxyProtocolConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
|
||||
Executor executor = destination.getHttpClient().getExecutor();
|
||||
Tag tag = (Tag)destination.getOrigin().getTag();
|
||||
if (tag == null)
|
||||
{
|
||||
InetSocketAddress local = endPoint.getLocalAddress();
|
||||
InetSocketAddress remote = endPoint.getRemoteAddress();
|
||||
boolean ipv4 = local.getAddress() instanceof Inet4Address;
|
||||
tag = new Tag(Tag.Command.PROXY, ipv4 ? Tag.Family.INET4 : Tag.Family.INET6, Tag.Protocol.STREAM, local.getAddress().getHostAddress(), local.getPort(), remote.getAddress().getHostAddress(), remote.getPort());
|
||||
}
|
||||
return new ProxyProtocolConnectionV2(endPoint, executor, getClientConnectionFactory(), context, tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>PROXY protocol version 2 metadata holder to be used in conjunction
|
||||
* with {@link org.eclipse.jetty.client.api.Request#tag(Object)}.</p>
|
||||
* <p>Instances of this class are associated to a destination so that
|
||||
* all connections of that destination will initiate the communication
|
||||
* with the PROXY protocol version 2 bytes specified by this metadata.</p>
|
||||
*/
|
||||
public static class Tag implements ClientConnectionFactory.Decorator
|
||||
{
|
||||
/**
|
||||
* The PROXY V2 Tag typically used to "ping" the server.
|
||||
*/
|
||||
public static final Tag LOCAL = new Tag(Command.LOCAL, Family.UNSPEC, Protocol.UNSPEC, null, 0, null, 0);
|
||||
|
||||
private Command command;
|
||||
private Family family;
|
||||
private Protocol protocol;
|
||||
private String srcIP;
|
||||
private int srcPort;
|
||||
private String dstIP;
|
||||
private int dstPort;
|
||||
private Map<Integer, byte[]> vectors;
|
||||
|
||||
/**
|
||||
* <p>Creates a Tag whose metadata will be derived from the underlying EndPoint.</p>
|
||||
*/
|
||||
public Tag()
|
||||
{
|
||||
this(null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a Tag with the given source metadata.</p>
|
||||
* <p>The destination metadata will be derived from the underlying EndPoint.</p>
|
||||
*
|
||||
* @param srcIP the source IP address
|
||||
* @param srcPort the source port
|
||||
*/
|
||||
public Tag(String srcIP, int srcPort)
|
||||
{
|
||||
this(Command.PROXY, null, Protocol.STREAM, srcIP, srcPort, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Creates a Tag with the given metadata.</p>
|
||||
*
|
||||
* @param command the LOCAL or PROXY command
|
||||
* @param family the protocol family
|
||||
* @param protocol the protocol type
|
||||
* @param srcIP the source IP address
|
||||
* @param srcPort the source port
|
||||
* @param dstIP the destination IP address
|
||||
* @param dstPort the destination port
|
||||
*/
|
||||
public Tag(Command command, Family family, Protocol protocol, String srcIP, int srcPort, String dstIP, int dstPort)
|
||||
{
|
||||
this.command = command;
|
||||
this.family = family;
|
||||
this.protocol = protocol;
|
||||
this.srcIP = srcIP;
|
||||
this.srcPort = srcPort;
|
||||
this.dstIP = dstIP;
|
||||
this.dstPort = dstPort;
|
||||
}
|
||||
|
||||
public void put(int type, byte[] data)
|
||||
{
|
||||
if (type < 0 || type > 255)
|
||||
throw new IllegalArgumentException("Invalid type: " + type);
|
||||
if (data != null && data.length > 65535)
|
||||
throw new IllegalArgumentException("Invalid data length: " + data.length);
|
||||
if (vectors == null)
|
||||
vectors = new HashMap<>();
|
||||
vectors.put(type, data);
|
||||
}
|
||||
|
||||
public Command getCommand()
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
public Family getFamily()
|
||||
{
|
||||
return family;
|
||||
}
|
||||
|
||||
public Protocol getProtocol()
|
||||
{
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public String getSourceAddress()
|
||||
{
|
||||
return srcIP;
|
||||
}
|
||||
|
||||
public int getSourcePort()
|
||||
{
|
||||
return srcPort;
|
||||
}
|
||||
|
||||
public String getDestinationAddress()
|
||||
{
|
||||
return dstIP;
|
||||
}
|
||||
|
||||
public int getDestinationPort()
|
||||
{
|
||||
return dstPort;
|
||||
}
|
||||
|
||||
public Map<Integer, byte[]> getVectors()
|
||||
{
|
||||
return vectors != null ? vectors : Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientConnectionFactory apply(ClientConnectionFactory factory)
|
||||
{
|
||||
return new V2(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null || getClass() != obj.getClass())
|
||||
return false;
|
||||
Tag that = (Tag)obj;
|
||||
return command == that.command &&
|
||||
family == that.family &&
|
||||
protocol == that.protocol &&
|
||||
Objects.equals(srcIP, that.srcIP) &&
|
||||
srcPort == that.srcPort &&
|
||||
Objects.equals(dstIP, that.dstIP) &&
|
||||
dstPort == that.dstPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(command, family, protocol, srcIP, srcPort, dstIP, dstPort);
|
||||
}
|
||||
|
||||
public enum Command
|
||||
{
|
||||
LOCAL, PROXY
|
||||
}
|
||||
|
||||
public enum Family
|
||||
{
|
||||
UNSPEC, INET4, INET6, UNIX
|
||||
}
|
||||
|
||||
public enum Protocol
|
||||
{
|
||||
UNSPEC, STREAM, DGRAM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ClientConnectionFactory factory;
|
||||
|
||||
private ProxyProtocolClientConnectionFactory(ClientConnectionFactory factory)
|
||||
{
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public ClientConnectionFactory getClientConnectionFactory()
|
||||
{
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
ProxyProtocolConnection connection = newProxyProtocolConnection(endPoint, context);
|
||||
return customize(connection, context);
|
||||
}
|
||||
|
||||
protected abstract ProxyProtocolConnection newProxyProtocolConnection(EndPoint endPoint, Map<String, Object> context);
|
||||
|
||||
private abstract static class ProxyProtocolConnection extends AbstractConnection implements Callback
|
||||
{
|
||||
protected static final Logger LOG = Log.getLogger(ProxyProtocolConnection.class);
|
||||
|
||||
private final ClientConnectionFactory factory;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
private ProxyProtocolConnection(EndPoint endPoint, Executor executor, ClientConnectionFactory factory, Map<String, Object> context)
|
||||
{
|
||||
super(endPoint, executor);
|
||||
this.factory = factory;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
writePROXYBytes(getEndPoint(), this);
|
||||
}
|
||||
|
||||
protected abstract void writePROXYBytes(EndPoint endPoint, Callback callback);
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
try
|
||||
{
|
||||
EndPoint endPoint = getEndPoint();
|
||||
Connection connection = factory.newConnection(endPoint, context);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Written PROXY line, upgrading to {}", connection);
|
||||
endPoint.upgrade(connection);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
close();
|
||||
Promise<?> promise = (Promise<?>)context.get(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
promise.failed(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFillable()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProxyProtocolConnectionV1 extends ProxyProtocolConnection
|
||||
{
|
||||
private final V1.Tag tag;
|
||||
|
||||
public ProxyProtocolConnectionV1(EndPoint endPoint, Executor executor, ClientConnectionFactory factory, Map<String, Object> context, V1.Tag tag)
|
||||
{
|
||||
super(endPoint, executor, factory, context);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePROXYBytes(EndPoint endPoint, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
InetSocketAddress localAddress = endPoint.getLocalAddress();
|
||||
InetSocketAddress remoteAddress = endPoint.getRemoteAddress();
|
||||
String family = tag.getFamily();
|
||||
String srcIP = tag.getSourceAddress();
|
||||
int srcPort = tag.getSourcePort();
|
||||
String dstIP = tag.getDestinationAddress();
|
||||
int dstPort = tag.getDestinationPort();
|
||||
if (family == null)
|
||||
family = localAddress.getAddress() instanceof Inet4Address ? "TCP4" : "TCP6";
|
||||
family = family.toUpperCase(Locale.ENGLISH);
|
||||
boolean unknown = family.equals("UNKNOWN");
|
||||
StringBuilder builder = new StringBuilder(64);
|
||||
builder.append("PROXY ").append(family);
|
||||
if (!unknown)
|
||||
{
|
||||
if (srcIP == null)
|
||||
srcIP = localAddress.getAddress().getHostAddress();
|
||||
builder.append(" ").append(srcIP);
|
||||
if (dstIP == null)
|
||||
dstIP = remoteAddress.getAddress().getHostAddress();
|
||||
builder.append(" ").append(dstIP);
|
||||
if (srcPort <= 0)
|
||||
srcPort = localAddress.getPort();
|
||||
builder.append(" ").append(srcPort);
|
||||
if (dstPort <= 0)
|
||||
dstPort = remoteAddress.getPort();
|
||||
builder.append(" ").append(dstPort);
|
||||
}
|
||||
builder.append("\r\n");
|
||||
String line = builder.toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Writing PROXY bytes: {}", line.trim());
|
||||
ByteBuffer buffer = ByteBuffer.wrap(line.getBytes(StandardCharsets.US_ASCII));
|
||||
endPoint.write(callback, buffer);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProxyProtocolConnectionV2 extends ProxyProtocolConnection
|
||||
{
|
||||
private static final byte[] MAGIC = {0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A};
|
||||
|
||||
private final V2.Tag tag;
|
||||
|
||||
public ProxyProtocolConnectionV2(EndPoint endPoint, Executor executor, ClientConnectionFactory factory, Map<String, Object> context, V2.Tag tag)
|
||||
{
|
||||
super(endPoint, executor, factory, context);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writePROXYBytes(EndPoint endPoint, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
int capacity = MAGIC.length;
|
||||
capacity += 1; // version and command
|
||||
capacity += 1; // family and protocol
|
||||
capacity += 2; // length
|
||||
capacity += 216; // max address length
|
||||
Map<Integer, byte[]> vectors = tag.getVectors();
|
||||
int vectorsLength = vectors.values().stream()
|
||||
.mapToInt(data -> 1 + 2 + data.length)
|
||||
.sum();
|
||||
capacity += vectorsLength;
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
|
||||
buffer.put(MAGIC);
|
||||
V2.Tag.Command command = tag.getCommand();
|
||||
int versionAndCommand = (2 << 4) | (command.ordinal() & 0x0F);
|
||||
buffer.put((byte)versionAndCommand);
|
||||
V2.Tag.Family family = tag.getFamily();
|
||||
String srcAddr = tag.getSourceAddress();
|
||||
if (srcAddr == null)
|
||||
srcAddr = endPoint.getLocalAddress().getAddress().getHostAddress();
|
||||
int srcPort = tag.getSourcePort();
|
||||
if (srcPort <= 0)
|
||||
srcPort = endPoint.getLocalAddress().getPort();
|
||||
if (family == null)
|
||||
family = InetAddress.getByName(srcAddr) instanceof Inet4Address ? V2.Tag.Family.INET4 : V2.Tag.Family.INET6;
|
||||
V2.Tag.Protocol protocol = tag.getProtocol();
|
||||
if (protocol == null)
|
||||
protocol = V2.Tag.Protocol.STREAM;
|
||||
int familyAndProtocol = (family.ordinal() << 4) | protocol.ordinal();
|
||||
buffer.put((byte)familyAndProtocol);
|
||||
int length = 0;
|
||||
switch (family)
|
||||
{
|
||||
case UNSPEC:
|
||||
break;
|
||||
case INET4:
|
||||
length = 12;
|
||||
break;
|
||||
case INET6:
|
||||
length = 36;
|
||||
break;
|
||||
case UNIX:
|
||||
length = 216;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
length += vectorsLength;
|
||||
buffer.putShort((short)length);
|
||||
String dstAddr = tag.getDestinationAddress();
|
||||
if (dstAddr == null)
|
||||
dstAddr = endPoint.getRemoteAddress().getAddress().getHostAddress();
|
||||
int dstPort = tag.getDestinationPort();
|
||||
if (dstPort <= 0)
|
||||
dstPort = endPoint.getRemoteAddress().getPort();
|
||||
switch (family)
|
||||
{
|
||||
case UNSPEC:
|
||||
break;
|
||||
case INET4:
|
||||
case INET6:
|
||||
buffer.put(InetAddress.getByName(srcAddr).getAddress());
|
||||
buffer.put(InetAddress.getByName(dstAddr).getAddress());
|
||||
buffer.putShort((short)srcPort);
|
||||
buffer.putShort((short)dstPort);
|
||||
break;
|
||||
case UNIX:
|
||||
int position = buffer.position();
|
||||
buffer.put(srcAddr.getBytes(StandardCharsets.US_ASCII));
|
||||
buffer.position(position + 108);
|
||||
buffer.put(dstAddr.getBytes(StandardCharsets.US_ASCII));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
for (Map.Entry<Integer, byte[]> entry : vectors.entrySet())
|
||||
{
|
||||
buffer.put(entry.getKey().byteValue());
|
||||
byte[] data = entry.getValue();
|
||||
buffer.putShort((short)data.length);
|
||||
buffer.put(data);
|
||||
}
|
||||
buffer.flip();
|
||||
endPoint.write(callback, buffer);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -180,6 +180,28 @@ public interface Request
|
|||
*/
|
||||
Request cookie(HttpCookie cookie);
|
||||
|
||||
/**
|
||||
* <p>Tags this request with the given metadata tag.</p>
|
||||
* <p>Each different tag will create a different destination,
|
||||
* even if the destination origin is the same.</p>
|
||||
* <p>This is particularly useful in proxies, where requests
|
||||
* for the same origin but from different clients may be tagged
|
||||
* with client's metadata (e.g. the client remote address).</p>
|
||||
* <p>The tag metadata class must correctly implement
|
||||
* {@link Object#hashCode()} and {@link Object#equals(Object)}
|
||||
* so that it can be used, along with the origin, to identify
|
||||
* a destination.</p>
|
||||
*
|
||||
* @param tag the metadata to tag the request with
|
||||
* @return this request object
|
||||
*/
|
||||
Request tag(Object tag);
|
||||
|
||||
/**
|
||||
* @return the metadata this request has been tagged with
|
||||
*/
|
||||
Object getTag();
|
||||
|
||||
/**
|
||||
* @param name the name of the attribute
|
||||
* @param value the value of the attribute
|
||||
|
|
|
@ -54,9 +54,9 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
* // Configure the clientConnector.
|
||||
*
|
||||
* // Prepare the application protocols.
|
||||
* HttpClientConnectionFactory.Key h1 = HttpClientConnectionFactory.HTTP;
|
||||
* ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP;
|
||||
* HTTP2Client http2Client = new HTTP2Client(clientConnector);
|
||||
* ClientConnectionFactory.Key h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
|
||||
* ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.H2(http2Client);
|
||||
*
|
||||
* // Create the HttpClientTransportDynamic, preferring h2 over h1.
|
||||
* HttpClientTransport transport = new HttpClientTransportDynamic(clientConnector, h2, h1);
|
||||
|
@ -67,17 +67,16 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
* <p>Note how in the code above the HttpClientTransportDynamic has been created with the <em>application
|
||||
* protocols</em> {@code h2} and {@code h1}, without the need to specify TLS (which is implied by the request
|
||||
* scheme) or ALPN (which is implied by HTTP/2 over TLS).</p>
|
||||
* <p>When a request is first sent, a destination needs to be created, and the {@link org.eclipse.jetty.client.Origin}
|
||||
* {@code (scheme, host, port)} is not enough to identify the destination because the same origin may speak
|
||||
* different protocols.
|
||||
* <p>When a request is first sent, {@code (scheme, host, port)} are not enough to identify the destination
|
||||
* because the same origin may speak different protocols.
|
||||
* For example, the Jetty server supports speaking clear-text {@code http/1.1} and {@code h2c} on the same port.
|
||||
* Imagine a client sending a {@code h2c} request to that port; this will create a destination and connections
|
||||
* that speak {@code h2c}; it won't be possible to use the connections from that destination to send
|
||||
* {@code http/1.1} requests.
|
||||
* Therefore a destination is identified by a {@link org.eclipse.jetty.client.HttpDestination.Key} and
|
||||
* applications can customize the creation of the destination key (for example depending on request protocol
|
||||
* Therefore a destination is identified by a {@link org.eclipse.jetty.client.Origin} and
|
||||
* applications can customize the creation of the origin (for example depending on request protocol
|
||||
* version, or request headers, or request attributes, or even request path) by overriding
|
||||
* {@link #newDestinationKey(HttpRequest, Origin)}.</p>
|
||||
* {@link HttpClientTransport#newOrigin(HttpRequest)}.</p>
|
||||
*/
|
||||
public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTransport
|
||||
{
|
||||
|
@ -119,7 +118,7 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
boolean ssl = HttpClient.isSchemeSecure(request.getScheme());
|
||||
String http1 = "http/1.1";
|
||||
|
@ -142,22 +141,23 @@ public class HttpClientTransportDynamic extends AbstractConnectorHttpClientTrans
|
|||
.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 && protocols.contains(http2)));
|
||||
Origin.Protocol protocol = null;
|
||||
if (!protocols.isEmpty())
|
||||
protocol = new Origin.Protocol(protocols, ssl && protocols.contains(http2));
|
||||
return getHttpClient().createOrigin(request, protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new MultiplexHttpDestination(getHttpClient(), key);
|
||||
return new MultiplexHttpDestination(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
{
|
||||
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||
HttpDestination.Protocol protocol = destination.getKey().getProtocol();
|
||||
Origin.Protocol protocol = destination.getOrigin().getProtocol();
|
||||
ClientConnectionFactory.Info factoryInfo;
|
||||
if (protocol == null)
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ 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 static final Origin.Protocol HTTP11 = new Origin.Protocol(List.of("http/1.1"), false);
|
||||
|
||||
private int headerCacheSize = 1024;
|
||||
private boolean headerCacheCaseSensitive;
|
||||
|
@ -62,15 +62,15 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
return new HttpDestination.Key(origin, HTTP11);
|
||||
return getHttpClient().createOrigin(request, HTTP11);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new DuplexHttpDestination(getHttpClient(), key);
|
||||
return new DuplexHttpDestination(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.ProxyConnectionFactory;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1;
|
||||
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V2;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HttpClientProxyProtocolTest
|
||||
{
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
private HttpClient client;
|
||||
|
||||
private void startServer(Handler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||
serverThreads.setName("server");
|
||||
server = new Server(serverThreads);
|
||||
HttpConnectionFactory http = new HttpConnectionFactory();
|
||||
ProxyConnectionFactory proxy = new ProxyConnectionFactory(http.getProtocol());
|
||||
connector = new ServerConnector(server, 1, 1, proxy, http);
|
||||
server.addConnector(connector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void startClient() throws Exception
|
||||
{
|
||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||
clientThreads.setName("client");
|
||||
client = new HttpClient();
|
||||
client.setExecutor(clientThreads);
|
||||
client.setRemoveIdleDestinations(false);
|
||||
client.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
if (server != null)
|
||||
server.stop();
|
||||
if (client != null)
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientProxyProtocolV1() throws Exception
|
||||
{
|
||||
startServer(new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
|
||||
response.getOutputStream().print(request.getRemotePort());
|
||||
}
|
||||
});
|
||||
startClient();
|
||||
|
||||
int serverPort = connector.getLocalPort();
|
||||
|
||||
int clientPort = ThreadLocalRandom.current().nextInt(1024, 65536);
|
||||
V1.Tag tag = new V1.Tag("127.0.0.1", clientPort);
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", serverPort)
|
||||
.tag(tag)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(String.valueOf(clientPort), response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientProxyProtocolV1Unknown() throws Exception
|
||||
{
|
||||
startServer(new EmptyServerHandler());
|
||||
startClient();
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.tag(V1.Tag.UNKNOWN)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientProxyProtocolV2() throws Exception
|
||||
{
|
||||
startServer(new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
|
||||
response.getOutputStream().print(request.getRemotePort());
|
||||
}
|
||||
});
|
||||
startClient();
|
||||
|
||||
int serverPort = connector.getLocalPort();
|
||||
|
||||
int clientPort = ThreadLocalRandom.current().nextInt(1024, 65536);
|
||||
V2.Tag tag = new V2.Tag("127.0.0.1", clientPort);
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", serverPort)
|
||||
.tag(tag)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(String.valueOf(clientPort), response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientProxyProtocolV2Local() throws Exception
|
||||
{
|
||||
startServer(new EmptyServerHandler());
|
||||
startClient();
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||
.tag(V2.Tag.LOCAL)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientProxyProtocolV2WithVectors() throws Exception
|
||||
{
|
||||
String tlsVersion = "TLSv1.3";
|
||||
byte[] tlsVersionBytes = tlsVersion.getBytes(StandardCharsets.US_ASCII);
|
||||
startServer(new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
EndPoint endPoint = jettyRequest.getHttpChannel().getEndPoint();
|
||||
assertTrue(endPoint instanceof ProxyConnectionFactory.ProxyEndPoint);
|
||||
ProxyConnectionFactory.ProxyEndPoint proxyEndPoint = (ProxyConnectionFactory.ProxyEndPoint)endPoint;
|
||||
assertEquals(tlsVersion, proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION));
|
||||
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
|
||||
response.getOutputStream().print(request.getRemotePort());
|
||||
}
|
||||
});
|
||||
startClient();
|
||||
|
||||
int serverPort = connector.getLocalPort();
|
||||
|
||||
int clientPort = ThreadLocalRandom.current().nextInt(1024, 65536);
|
||||
V2.Tag tag = new V2.Tag("127.0.0.1", clientPort);
|
||||
int typeTLS = 0x20;
|
||||
byte[] dataTLS = new byte[1 + 4 + (1 + 2 + tlsVersionBytes.length)];
|
||||
dataTLS[0] = 0x01; // CLIENT_SSL
|
||||
dataTLS[5] = 0x21; // SUBTYPE_SSL_VERSION
|
||||
dataTLS[6] = 0x00; // Length, hi byte
|
||||
dataTLS[7] = (byte)tlsVersionBytes.length; // Length, lo byte
|
||||
System.arraycopy(tlsVersionBytes, 0, dataTLS, 8, tlsVersionBytes.length);
|
||||
tag.put(typeTLS, dataTLS);
|
||||
|
||||
ContentResponse response = client.newRequest("localhost", serverPort)
|
||||
.tag(tag)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(String.valueOf(clientPort), response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyProtocolWrappingHTTPProxy() throws Exception
|
||||
{
|
||||
startServer(new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setContentType(MimeTypes.Type.TEXT_PLAIN.asString());
|
||||
response.getOutputStream().print(request.getRemotePort());
|
||||
}
|
||||
});
|
||||
startClient();
|
||||
|
||||
int proxyPort = connector.getLocalPort();
|
||||
int serverPort = proxyPort + 1; // Any port will do.
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyPort));
|
||||
|
||||
// We are simulating to be a HttpClient inside a proxy.
|
||||
// The server is configured with the PROXY protocol to know the socket address of clients.
|
||||
|
||||
// The proxy receives a request from the client, and it extracts the client address.
|
||||
int clientPort = ThreadLocalRandom.current().nextInt(1024, 65536);
|
||||
V1.Tag tag = new V1.Tag("127.0.0.1", clientPort);
|
||||
|
||||
// The proxy maps the client address, then sends the request.
|
||||
ContentResponse response = client.newRequest("localhost", serverPort)
|
||||
.tag(tag)
|
||||
.header(HttpHeader.CONNECTION, "close")
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(String.valueOf(clientPort), response.getContentAsString());
|
||||
List<Destination> destinations = client.getDestinations();
|
||||
assertEquals(1, destinations.size());
|
||||
HttpDestination destination = (HttpDestination)destinations.get(0);
|
||||
assertTrue(destination.getConnectionPool().isEmpty());
|
||||
|
||||
// The previous connection has been closed.
|
||||
// Make another request from the same client address.
|
||||
response = client.newRequest("localhost", serverPort)
|
||||
.tag(tag)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(String.valueOf(clientPort), response.getContentAsString());
|
||||
destinations = client.getDestinations();
|
||||
assertEquals(1, destinations.size());
|
||||
assertSame(destination, destinations.get(0));
|
||||
|
||||
// Make another request from a different client address.
|
||||
int clientPort2 = clientPort + 1;
|
||||
V1.Tag tag2 = new V1.Tag("127.0.0.1", clientPort2);
|
||||
response = client.newRequest("localhost", serverPort)
|
||||
.tag(tag2)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals(String.valueOf(clientPort2), response.getContentAsString());
|
||||
destinations = client.getDestinations();
|
||||
assertEquals(2, destinations.size());
|
||||
}
|
||||
}
|
|
@ -657,7 +657,7 @@ public class HttpClientTLSTest
|
|||
|
||||
// Create a connection but don't use it.
|
||||
Origin origin = new Origin(HttpScheme.HTTPS.asString(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = client.resolveDestination(new HttpDestination.Key(origin, null));
|
||||
HttpDestination destination = client.resolveDestination(origin);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
// Trigger the creation of a new connection, but don't use it.
|
||||
connectionPool.tryCreate(-1);
|
||||
|
@ -755,7 +755,7 @@ public class HttpClientTLSTest
|
|||
|
||||
// Create a connection but don't use it.
|
||||
Origin origin = new Origin(HttpScheme.HTTPS.asString(), "localhost", connector.getLocalPort());
|
||||
HttpDestination destination = client.resolveDestination(new HttpDestination.Key(origin, null));
|
||||
HttpDestination destination = client.resolveDestination(origin);
|
||||
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
|
||||
// Trigger the creation of a new connection, but don't use it.
|
||||
connectionPool.tryCreate(-1);
|
||||
|
|
|
@ -75,15 +75,15 @@ public class HttpClientTransportOverFCGI extends AbstractConnectorHttpClientTran
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(List.of("fastcgi/1.1"), false));
|
||||
return getHttpClient().createOrigin(request, new Origin.Protocol(List.of("fastcgi/1.1"), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new DuplexHttpDestination(getHttpClient(), key);
|
||||
return new DuplexHttpDestination(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -108,16 +108,16 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
String protocol = HttpScheme.HTTPS.is(origin.getScheme()) ? "h2" : "h2c";
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(List.of(protocol), false));
|
||||
String protocol = HttpScheme.HTTPS.is(request.getScheme()) ? "h2" : "h2c";
|
||||
return getHttpClient().createOrigin(request, new Origin.Protocol(List.of(protocol), false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new MultiplexHttpDestination(getHttpClient(), key);
|
||||
return new MultiplexHttpDestination(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -345,7 +345,7 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
});
|
||||
|
||||
int proxyPort = connector.getLocalPort();
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new HttpDestination.Protocol(List.of("h2c"), false)));
|
||||
client.getProxyConfiguration().getProxies().add(new HttpProxy(new Origin.Address("localhost", proxyPort), false, new Origin.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)
|
||||
|
|
|
@ -48,6 +48,23 @@ public interface ClientConnectionFactory
|
|||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Wraps another ClientConnectionFactory.</p>
|
||||
* <p>This is typically done by protocols that send "preface" bytes with some metadata
|
||||
* before other protocols. The metadata could be, for example, proxying information
|
||||
* or authentication information.</p>
|
||||
*/
|
||||
interface Decorator
|
||||
{
|
||||
/**
|
||||
* <p>Wraps the given {@code factory}.</p>
|
||||
*
|
||||
* @param factory the ClientConnectionFactory to wrap
|
||||
* @return the wrapping ClientConnectionFactory
|
||||
*/
|
||||
ClientConnectionFactory apply(ClientConnectionFactory factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A holder for a list of protocol strings identifying a network protocol
|
||||
* (for example {@code ["h2", "h2-17", "h2-16"]}) and a {@link ClientConnectionFactory}
|
||||
|
|
|
@ -31,12 +31,14 @@ import java.util.function.Supplier;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.QuotedCSV;
|
||||
import org.eclipse.jetty.http.pathmap.PathMappings;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.DateCache;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
|
@ -233,8 +235,9 @@ import static java.lang.invoke.MethodType.methodType;
|
|||
* <tr>
|
||||
* <td valign="top">%{d}u</td>
|
||||
* <td>
|
||||
* Remote user if the request was authenticated. May be bogus if return status (%s) is 401 (unauthorized).
|
||||
* Optional parameter d, with this parameter deferred authentication will also be checked.
|
||||
* Remote user if the request was authenticated with servlet authentication. May be bogus if return status (%s) is 401 (unauthorized).
|
||||
* Optional parameter d, with this parameter deferred authentication will also be checked,
|
||||
* this is equivalent to {@link HttpServletRequest#getRemoteUser()}.
|
||||
* </td>
|
||||
* </tr>
|
||||
*
|
||||
|
@ -294,11 +297,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
{
|
||||
_logHandle = getLogHandle(formatString);
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalAccessException e)
|
||||
catch (NoSuchMethodException | IllegalAccessException e)
|
||||
{
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
@ -357,20 +356,14 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
protected static String getAuthentication(Request request, boolean checkDeferred)
|
||||
{
|
||||
Authentication authentication = request.getAuthentication();
|
||||
if (checkDeferred && authentication instanceof Authentication.Deferred)
|
||||
authentication = ((Authentication.Deferred)authentication).authenticate(request);
|
||||
|
||||
String name = null;
|
||||
|
||||
boolean deferred = false;
|
||||
if (checkDeferred && authentication instanceof Authentication.Deferred)
|
||||
{
|
||||
authentication = ((Authentication.Deferred)authentication).authenticate(request);
|
||||
deferred = true;
|
||||
}
|
||||
|
||||
if (authentication instanceof Authentication.User)
|
||||
name = ((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName();
|
||||
|
||||
return (name == null) ? null : (deferred ? ("?" + name) : name);
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -415,9 +408,9 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
if (_ignorePaths != null && _ignorePaths.length > 0)
|
||||
{
|
||||
_ignorePathMap = new PathMappings<>();
|
||||
for (int i = 0; i < _ignorePaths.length; i++)
|
||||
for (String ignorePath : _ignorePaths)
|
||||
{
|
||||
_ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
|
||||
_ignorePathMap.put(ignorePath, ignorePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -442,8 +435,8 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
private MethodHandle getLogHandle(String formatString) throws NoSuchMethodException, IllegalAccessException
|
||||
{
|
||||
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));
|
||||
MethodHandle append = lookup.findStatic(CustomRequestLog.class, "append", methodType(void.class, String.class, StringBuilder.class));
|
||||
MethodHandle logHandle = lookup.findStatic(CustomRequestLog.class, "logNothing", methodType(void.class, StringBuilder.class, Request.class, Response.class));
|
||||
|
||||
List<Token> tokens = getTokens(formatString);
|
||||
Collections.reverse(tokens);
|
||||
|
@ -486,7 +479,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
String arg = m.group("ARG");
|
||||
String modifierString = m.group("MOD");
|
||||
|
||||
Boolean negated = false;
|
||||
boolean negated = false;
|
||||
if (modifierString != null)
|
||||
{
|
||||
if (modifierString.startsWith("!"))
|
||||
|
@ -581,8 +574,8 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
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);
|
||||
MethodType logType = methodType(void.class, StringBuilder.class, Request.class, Response.class);
|
||||
MethodType logTypeArg = methodType(void.class, String.class, StringBuilder.class, Request.class, Response.class);
|
||||
|
||||
//TODO should we throw IllegalArgumentExceptions when given arguments for codes which do not take them
|
||||
MethodHandle specificHandle;
|
||||
|
@ -596,7 +589,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "a":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
arg = "server";
|
||||
|
||||
String method;
|
||||
|
@ -628,7 +621,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "p":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
arg = "server";
|
||||
|
||||
String method;
|
||||
|
@ -662,7 +655,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
case "I":
|
||||
{
|
||||
String method;
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
method = "logBytesReceived";
|
||||
else if (arg.equalsIgnoreCase("clf"))
|
||||
method = "logBytesReceivedCLF";
|
||||
|
@ -676,7 +669,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
case "O":
|
||||
{
|
||||
String method;
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
method = "logBytesSent";
|
||||
else if (arg.equalsIgnoreCase("clf"))
|
||||
method = "logBytesSentCLF";
|
||||
|
@ -690,7 +683,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
case "S":
|
||||
{
|
||||
String method;
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
method = "logBytesTransferred";
|
||||
else if (arg.equalsIgnoreCase("clf"))
|
||||
method = "logBytesTransferredCLF";
|
||||
|
@ -703,7 +696,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "C":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
{
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestCookies", logType);
|
||||
}
|
||||
|
@ -723,7 +716,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "e":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
throw new IllegalArgumentException("No arg for %e");
|
||||
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logEnvironmentVar", logTypeArg);
|
||||
|
@ -745,7 +738,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "i":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
throw new IllegalArgumentException("No arg for %i");
|
||||
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestHeader", logTypeArg);
|
||||
|
@ -767,7 +760,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "o":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
throw new IllegalArgumentException("No arg for %o");
|
||||
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logResponseHeader", logTypeArg);
|
||||
|
@ -832,7 +825,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
DateCache logDateCache = new DateCache(format, locale, timeZone);
|
||||
|
||||
MethodType logTypeDateCache = methodType(Void.TYPE, DateCache.class, StringBuilder.class, Request.class, Response.class);
|
||||
MethodType logTypeDateCache = methodType(void.class, DateCache.class, StringBuilder.class, Request.class, Response.class);
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestTime", logTypeDateCache);
|
||||
specificHandle = specificHandle.bindTo(logDateCache);
|
||||
break;
|
||||
|
@ -866,10 +859,12 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
case "u":
|
||||
{
|
||||
String method;
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
method = "logRequestAuthentication";
|
||||
else if ("d".equals(arg))
|
||||
method = "logRequestAuthenticationWithDeferred";
|
||||
else
|
||||
method = "logRequestAuthentication";
|
||||
throw new IllegalArgumentException("Invalid arg for %u: " + arg);
|
||||
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, method, logType);
|
||||
break;
|
||||
|
@ -889,7 +884,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "ti":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
throw new IllegalArgumentException("No arg for %ti");
|
||||
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logRequestTrailer", logTypeArg);
|
||||
|
@ -899,7 +894,7 @@ public class CustomRequestLog extends ContainerLifeCycle implements RequestLog
|
|||
|
||||
case "to":
|
||||
{
|
||||
if (arg == null || arg.isEmpty())
|
||||
if (StringUtil.isEmpty(arg))
|
||||
throw new IllegalArgumentException("No arg for %to");
|
||||
|
||||
specificHandle = lookup.findStatic(CustomRequestLog.class, "logResponseTrailer", logTypeArg);
|
||||
|
|
|
@ -102,9 +102,10 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
|
||||
public class ProxyProtocolV1orV2Connection extends AbstractConnection
|
||||
{
|
||||
// Only do a tiny read to figure out what PROXY version it is.
|
||||
private final ByteBuffer _buffer = BufferUtil.allocate(16);
|
||||
private final Connector _connector;
|
||||
private final String _next;
|
||||
private ByteBuffer _buffer = BufferUtil.allocate(16);
|
||||
|
||||
protected ProxyProtocolV1orV2Connection(EndPoint endp, Connector connector, String next)
|
||||
{
|
||||
|
@ -157,8 +158,11 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOG.warn("Not PROXY protocol for {}", getEndPoint());
|
||||
close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable x)
|
||||
|
@ -179,8 +183,8 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
private final Connector _connector;
|
||||
private final String _next;
|
||||
private final StringBuilder _builder = new StringBuilder();
|
||||
private final String[] _field = new String[6];
|
||||
private int _fields;
|
||||
private final String[] _fields = new String[6];
|
||||
private int _index;
|
||||
private int _length;
|
||||
|
||||
protected ProxyProtocolV1Connection(EndPoint endp, Connector connector, String next, ByteBuffer buffer)
|
||||
|
@ -201,16 +205,18 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
|
||||
private boolean parse(ByteBuffer buffer)
|
||||
{
|
||||
// parse fields
|
||||
// Parse fields
|
||||
while (buffer.hasRemaining())
|
||||
{
|
||||
byte b = buffer.get();
|
||||
if (_fields < 6)
|
||||
if (_index < 6)
|
||||
{
|
||||
if (b == ' ' || b == '\r' && _fields == 5)
|
||||
if (b == ' ' || b == '\r')
|
||||
{
|
||||
_field[_fields++] = _builder.toString();
|
||||
_fields[_index++] = _builder.toString();
|
||||
_builder.setLength(0);
|
||||
if (b == '\r')
|
||||
_index = 6;
|
||||
}
|
||||
else if (b < ' ')
|
||||
{
|
||||
|
@ -227,7 +233,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
{
|
||||
if (b == '\n')
|
||||
{
|
||||
_fields = 7;
|
||||
_index = 7;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -245,12 +251,12 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
try
|
||||
{
|
||||
ByteBuffer buffer = null;
|
||||
while (_fields < 7)
|
||||
while (_index < 7)
|
||||
{
|
||||
// Create a buffer that will not read too much data
|
||||
// since once read it is impossible to push back for the
|
||||
// real connection to read it.
|
||||
int size = Math.max(1, SIZE[_fields] - _builder.length());
|
||||
int size = Math.max(1, SIZE[_index] - _builder.length());
|
||||
if (buffer == null || buffer.capacity() != size)
|
||||
buffer = BufferUtil.allocate(size);
|
||||
else
|
||||
|
@ -282,22 +288,34 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory
|
|||
}
|
||||
|
||||
// Check proxy
|
||||
if (!"PROXY".equals(_field[0]))
|
||||
if (!"PROXY".equals(_fields[0]))
|
||||
{
|
||||
LOG.warn("Not PROXY protocol for {}", getEndPoint());
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract Addresses
|
||||
InetSocketAddress remote = new InetSocketAddress(_field[2], Integer.parseInt(_field[4]));
|
||||
InetSocketAddress local = new InetSocketAddress(_field[3], Integer.parseInt(_field[5]));
|
||||
String srcIP = _fields[2];
|
||||
String srcPort = _fields[4];
|
||||
String dstIP = _fields[3];
|
||||
String dstPort = _fields[5];
|
||||
// If UNKNOWN, we must ignore the information sent, so use the EndPoint's.
|
||||
boolean unknown = "UNKNOWN".equalsIgnoreCase(_fields[1]);
|
||||
if (unknown)
|
||||
{
|
||||
srcIP = getEndPoint().getRemoteAddress().getAddress().getHostAddress();
|
||||
srcPort = String.valueOf(getEndPoint().getRemoteAddress().getPort());
|
||||
dstIP = getEndPoint().getLocalAddress().getAddress().getHostAddress();
|
||||
dstPort = String.valueOf(getEndPoint().getLocalAddress().getPort());
|
||||
}
|
||||
InetSocketAddress remote = new InetSocketAddress(srcIP, Integer.parseInt(srcPort));
|
||||
InetSocketAddress local = new InetSocketAddress(dstIP, Integer.parseInt(dstPort));
|
||||
|
||||
// Create the next protocol
|
||||
ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next);
|
||||
if (connectionFactory == null)
|
||||
{
|
||||
LOG.warn("No Next protocol '{}' for {}", _next, getEndPoint());
|
||||
LOG.warn("No next protocol '{}' for {}", _next, getEndPoint());
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -30,9 +30,6 @@ import org.junit.jupiter.api.Test;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ProxyConnectionTest
|
||||
{
|
||||
private Server _server;
|
||||
|
@ -85,7 +82,7 @@ public class ProxyConnectionTest
|
|||
public void testIPv6() throws Exception
|
||||
{
|
||||
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
|
||||
String response = _connector.getResponse("PROXY UNKNOWN eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n" +
|
||||
String response = _connector.getResponse("PROXY TCP6 eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n" +
|
||||
"GET /path HTTP/1.1\n" +
|
||||
"Host: server:80\n" +
|
||||
"Connection: close\n" +
|
||||
|
|
|
@ -74,15 +74,15 @@ public class HttpClientTransportOverUnixSockets extends AbstractConnectorHttpCli
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
return new HttpDestination.Key(origin, null);
|
||||
return getHttpClient().createOrigin(request, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
public HttpDestination newHttpDestination(Origin origin)
|
||||
{
|
||||
return new DuplexHttpDestination(getHttpClient(), key);
|
||||
return new DuplexHttpDestination(getHttpClient(), origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.http.client;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -35,6 +36,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
|
@ -649,6 +651,30 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
|
|||
assertEquals(0, response.getContent().length);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ArgumentsSource(TransportProvider.class)
|
||||
public void testOneDestinationPerUser(Transport transport) throws Exception
|
||||
{
|
||||
init(transport);
|
||||
scenario.start(new EmptyServerHandler());
|
||||
|
||||
int runs = 4;
|
||||
int users = 16;
|
||||
for (int i = 0; i < runs; ++i)
|
||||
{
|
||||
for (int j = 0; j < users; ++j)
|
||||
{
|
||||
ContentResponse response = scenario.client.newRequest(scenario.newURI())
|
||||
.tag(j)
|
||||
.send();
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
List<Destination> destinations = scenario.client.getDestinations();
|
||||
assertEquals(users, destinations.size());
|
||||
}
|
||||
|
||||
private void sleep(long time) throws IOException
|
||||
{
|
||||
try
|
||||
|
|
|
@ -20,8 +20,6 @@ package org.eclipse.jetty.http.client;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Function;
|
||||
|
@ -32,13 +30,11 @@ import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
|
|||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.MultiplexHttpDestination;
|
||||
import org.eclipse.jetty.client.Origin;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Destination;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
|
||||
import org.eclipse.jetty.client.proxy.ProxyProtocolClientConnectionFactory;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
|
@ -62,6 +58,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.eclipse.jetty.client.ProxyProtocolClientConnectionFactory.V1;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
|
@ -255,11 +252,11 @@ public class HttpClientTransportDynamicTest
|
|||
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(clientConnector, h1, h2c)
|
||||
{
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
public Origin newOrigin(HttpRequest request)
|
||||
{
|
||||
// Use prior-knowledge, i.e. negotiate==false.
|
||||
List<String> protocols = HttpVersion.HTTP_2 == request.getVersion() ? h2c.getProtocols() : h1.getProtocols();
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(protocols, false));
|
||||
return new Origin(request.getScheme(), request.getHost(), request.getPort(), request.getTag(), new Origin.Protocol(protocols, false));
|
||||
}
|
||||
};
|
||||
client = new HttpClient(transport);
|
||||
|
@ -285,8 +282,8 @@ public class HttpClientTransportDynamicTest
|
|||
assertEquals(2, destinations.size());
|
||||
assertEquals(1, destinations.stream()
|
||||
.map(HttpDestination.class::cast)
|
||||
.map(HttpDestination::getKey)
|
||||
.map(HttpDestination.Key::getOrigin)
|
||||
.map(HttpDestination::getOrigin)
|
||||
.map(Origin::asString)
|
||||
.distinct()
|
||||
.count());
|
||||
}
|
||||
|
@ -323,8 +320,8 @@ public class HttpClientTransportDynamicTest
|
|||
assertEquals(2, destinations.size());
|
||||
assertEquals(1, destinations.stream()
|
||||
.map(HttpDestination.class::cast)
|
||||
.map(HttpDestination::getKey)
|
||||
.map(HttpDestination.Key::getOrigin)
|
||||
.map(HttpDestination::getOrigin)
|
||||
.map(Origin::asString)
|
||||
.distinct()
|
||||
.count());
|
||||
}
|
||||
|
@ -402,42 +399,21 @@ public class HttpClientTransportDynamicTest
|
|||
clientConnector.setSslContextFactory(newClientSslContextFactory());
|
||||
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
|
||||
|
||||
Map<HttpRequest, String> mapping = new ConcurrentHashMap<>();
|
||||
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1)
|
||||
{
|
||||
@Override
|
||||
public HttpDestination.Key newDestinationKey(HttpRequest request, Origin origin)
|
||||
{
|
||||
String kind = mapping.remove(request);
|
||||
return new HttpDestination.Key(origin, new HttpDestination.Protocol(List.of("http/1.1"), false), kind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpDestination newHttpDestination(HttpDestination.Key key)
|
||||
{
|
||||
// Here we want to wrap the destination with the PROXY
|
||||
// protocol, for a specific remote client socket address.
|
||||
return new MultiplexHttpDestination(client, key, factory -> new ProxyProtocolClientConnectionFactory(factory, () ->
|
||||
{
|
||||
String[] address = key.getKind().split(":");
|
||||
return new Origin.Address(address[0], Integer.parseInt(address[1]));
|
||||
}));
|
||||
}
|
||||
});
|
||||
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1));
|
||||
client.start();
|
||||
|
||||
// Simulate a proxy request to the server.
|
||||
HttpRequest proxyRequest1 = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
|
||||
// Map the proxy request to client IP:port.
|
||||
int clientPort1 = ThreadLocalRandom.current().nextInt(1024, 65536);
|
||||
mapping.put(proxyRequest1, "localhost:" + clientPort1);
|
||||
proxyRequest1.tag(new V1.Tag("localhost", clientPort1));
|
||||
ContentResponse proxyResponse1 = proxyRequest1.send();
|
||||
assertEquals(String.valueOf(clientPort1), proxyResponse1.getContentAsString());
|
||||
|
||||
// Simulate another request to the server, from a different client port.
|
||||
HttpRequest proxyRequest2 = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
|
||||
int clientPort2 = ThreadLocalRandom.current().nextInt(1024, 65536);
|
||||
mapping.put(proxyRequest2, "localhost:" + clientPort2);
|
||||
proxyRequest2.tag(new V1.Tag("localhost", clientPort2));
|
||||
ContentResponse proxyResponse2 = proxyRequest2.send();
|
||||
assertEquals(String.valueOf(clientPort2), proxyResponse2.getContentAsString());
|
||||
|
||||
|
@ -446,8 +422,8 @@ public class HttpClientTransportDynamicTest
|
|||
assertEquals(2, destinations.size());
|
||||
assertEquals(1, destinations.stream()
|
||||
.map(HttpDestination.class::cast)
|
||||
.map(HttpDestination::getKey)
|
||||
.map(HttpDestination.Key::getOrigin)
|
||||
.map(HttpDestination::getOrigin)
|
||||
.map(Origin::asString)
|
||||
.distinct()
|
||||
.count());
|
||||
}
|
||||
|
@ -500,8 +476,8 @@ public class HttpClientTransportDynamicTest
|
|||
assertEquals(4, destinations.size());
|
||||
assertEquals(2, destinations.stream()
|
||||
.map(HttpDestination.class::cast)
|
||||
.map(HttpDestination::getKey)
|
||||
.map(HttpDestination.Key::getOrigin)
|
||||
.map(HttpDestination::getOrigin)
|
||||
.map(Origin::asString)
|
||||
.distinct()
|
||||
.count());
|
||||
}
|
||||
|
|
|
@ -224,35 +224,35 @@ public class ProxyWithDynamicTransportTest
|
|||
var h2 = List.of("h2");
|
||||
return java.util.stream.Stream.of(
|
||||
// HTTP/1.1 Proxy with HTTP/1.1 Server.
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), false, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), false, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), true, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), true, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_1_1, true),
|
||||
// HTTP/1.1 Proxy with HTTP/2 Server.
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), false, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), false, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), true, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h1, false), true, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new Origin.Protocol(h1, false), false, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new Origin.Protocol(h1, false), true, HttpVersion.HTTP_2, true),
|
||||
// HTTP/2 Proxy with HTTP/1.1 Server.
|
||||
Arguments.of(new HttpDestination.Protocol(h2c, false), false, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h2c, false), false, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, false), true, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, false), true, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, true), true, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, true), true, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_1_1, true),
|
||||
Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_1_1, false),
|
||||
Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_1_1, true),
|
||||
// HTTP/2 Proxy with HTTP/2 Server.
|
||||
Arguments.of(new HttpDestination.Protocol(h2c, false), false, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h2c, false), false, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, false), true, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, false), true, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, true), true, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new HttpDestination.Protocol(h2, true), true, HttpVersion.HTTP_2, true)
|
||||
Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new Origin.Protocol(h2c, false), false, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new Origin.Protocol(h2, false), true, HttpVersion.HTTP_2, true),
|
||||
Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_2, false),
|
||||
Arguments.of(new Origin.Protocol(h2, true), true, HttpVersion.HTTP_2, true)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "proxyProtocol={0}, proxySecure={1}, serverProtocol={2}, serverSecure={3}")
|
||||
@MethodSource("testParams")
|
||||
public void testProxy(HttpDestination.Protocol proxyProtocol, boolean proxySecure, HttpVersion serverProtocol, boolean serverSecure) throws Exception
|
||||
public void testProxy(Origin.Protocol proxyProtocol, boolean proxySecure, HttpVersion serverProtocol, boolean serverSecure) throws Exception
|
||||
{
|
||||
int status = HttpStatus.NO_CONTENT_204;
|
||||
start(new EmptyServerHandler()
|
||||
|
@ -302,7 +302,7 @@ public class ProxyWithDynamicTransportTest
|
|||
|
||||
int proxyPort = proxyConnector.getLocalPort();
|
||||
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||
HttpProxy proxy = new HttpProxy(proxyAddress, false, new HttpDestination.Protocol(List.of("h2c"), false));
|
||||
HttpProxy proxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
|
||||
client.getProxyConfiguration().getProxies().add(proxy);
|
||||
|
||||
long idleTimeout = 1000;
|
||||
|
@ -343,7 +343,7 @@ public class ProxyWithDynamicTransportTest
|
|||
|
||||
int proxyPort = proxyConnector.getLocalPort();
|
||||
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new HttpDestination.Protocol(List.of("h2c"), false));
|
||||
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
|
||||
client.getProxyConfiguration().getProxies().add(httpProxy);
|
||||
proxy.stop();
|
||||
|
||||
|
@ -378,7 +378,7 @@ public class ProxyWithDynamicTransportTest
|
|||
|
||||
int proxyPort = proxyConnector.getLocalPort();
|
||||
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new HttpDestination.Protocol(List.of("h2c"), false));
|
||||
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
|
||||
client.getProxyConfiguration().getProxies().add(httpProxy);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.handler;
|
||||
package org.eclipse.jetty.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -26,15 +26,25 @@ import java.net.NetworkInterface;
|
|||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||
import org.eclipse.jetty.security.HashLoginService;
|
||||
import org.eclipse.jetty.security.SecurityHandler;
|
||||
import org.eclipse.jetty.security.UserStore;
|
||||
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
|
||||
import org.eclipse.jetty.server.CustomRequestLog;
|
||||
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
|
@ -44,8 +54,12 @@ import org.eclipse.jetty.server.Request;
|
|||
import org.eclipse.jetty.server.RequestLog;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.util.DateCache;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.util.security.Credential;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
|
@ -88,24 +102,70 @@ public class CustomRequestLogTest
|
|||
TestRequestLogWriter writer = new TestRequestLogWriter();
|
||||
_log = new CustomRequestLog(writer, formatString);
|
||||
_server.setRequestLog(_log);
|
||||
_server.setHandler(new TestHandler());
|
||||
ServletContextHandler contextHandler = new ServletContextHandler();
|
||||
contextHandler.setSecurityHandler(getSecurityHandler("username", "password", "testRealm"));
|
||||
contextHandler.addServlet(new ServletHolder(new TestServlet()), "/");
|
||||
_server.setHandler(contextHandler);
|
||||
_server.start();
|
||||
|
||||
String host = _serverConnector.getHost();
|
||||
if (host == null)
|
||||
{
|
||||
host = "localhost";
|
||||
}
|
||||
|
||||
int localPort = _serverConnector.getLocalPort();
|
||||
_serverURI = new URI(String.format("http://%s:%d/", host, localPort));
|
||||
}
|
||||
|
||||
private static SecurityHandler getSecurityHandler(String username, String password, String realm)
|
||||
{
|
||||
HashLoginService loginService = new HashLoginService();
|
||||
UserStore userStore = new UserStore();
|
||||
userStore.addUser(username, Credential.getCredential(password), new String[]{"user"});
|
||||
loginService.setUserStore(userStore);
|
||||
loginService.setName(realm);
|
||||
|
||||
Constraint constraint = new Constraint();
|
||||
constraint.setName("auth");
|
||||
constraint.setAuthenticate(true);
|
||||
constraint.setRoles(new String[]{"**"});
|
||||
|
||||
ConstraintMapping mapping = new ConstraintMapping();
|
||||
mapping.setPathSpec("/secure/*");
|
||||
mapping.setConstraint(constraint);
|
||||
|
||||
ConstraintSecurityHandler security = new ConstraintSecurityHandler();
|
||||
security.addConstraintMapping(mapping);
|
||||
security.setAuthenticator(new BasicAuthenticator());
|
||||
security.setLoginService(loginService);
|
||||
|
||||
return security;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception
|
||||
{
|
||||
_server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogRemoteUser() throws Exception
|
||||
{
|
||||
String authHeader = HttpHeader.AUTHORIZATION + ": Basic " + Base64.getEncoder().encodeToString("username:password".getBytes());
|
||||
testHandlerServerStart("%u %{d}u");
|
||||
|
||||
_connector.getResponse("GET / HTTP/1.0\n\n\n");
|
||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(log, is("- -"));
|
||||
|
||||
_connector.getResponse("GET / HTTP/1.0\n" + authHeader + "\n\n\n");
|
||||
log = _entries.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(log, is("- username"));
|
||||
|
||||
_connector.getResponse("GET /secure HTTP/1.0\n" + authHeader + "\n\n\n");
|
||||
log = _entries.poll(5, TimeUnit.SECONDS);
|
||||
assertThat(log, is("username username"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifier() throws Exception
|
||||
{
|
||||
|
@ -374,7 +434,7 @@ public class CustomRequestLogTest
|
|||
_connector.getResponse("GET / HTTP/1.0\n\n");
|
||||
String log = _entries.poll(5, TimeUnit.SECONDS);
|
||||
long requestTime = requestTimes.poll(5, TimeUnit.SECONDS);
|
||||
DateCache dateCache = new DateCache(_log.DEFAULT_DATE_FORMAT, Locale.getDefault(), "GMT");
|
||||
DateCache dateCache = new DateCache(CustomRequestLog.DEFAULT_DATE_FORMAT, Locale.getDefault(), "GMT");
|
||||
assertThat(log, is("RequestTime: [" + dateCache.format(requestTime) + "]"));
|
||||
}
|
||||
|
||||
|
@ -549,11 +609,13 @@ public class CustomRequestLogTest
|
|||
}
|
||||
}
|
||||
|
||||
private class TestHandler extends AbstractHandler
|
||||
private class TestServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Request baseRequest = Objects.requireNonNull(Request.getBaseRequest(request));
|
||||
|
||||
if (request.getRequestURI().contains("error404"))
|
||||
{
|
||||
response.setStatus(404);
|
||||
|
@ -596,10 +658,7 @@ public class CustomRequestLogTest
|
|||
if (request.getContentLength() > 0)
|
||||
{
|
||||
InputStream in = request.getInputStream();
|
||||
while (in.read() > 0)
|
||||
{
|
||||
;
|
||||
}
|
||||
while (in.read() > 0);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue