Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.

This commit is contained in:
Simone Bordet 2019-12-17 13:41:15 +01:00
commit b1e2f80017
28 changed files with 1433 additions and 502 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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