Implemented ProxyHandler in new module jetty-core/jetty-proxy. (#8475)
Moved ConnectHandler to org.eclipse.jetty.server.handler, where it should have been from the start. Cleaned up and corrected behavior of SecureRequestCustomizer. Now the response is wrapped with isSecure=true if it arrived over a secure transport. The request URI scheme indicates whether the request is secure towards the origin server. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
a87c9fec44
commit
a3ba86266a
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
@ -363,12 +364,19 @@ public class HttpField
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getValues()
|
public String[] getValues()
|
||||||
|
{
|
||||||
|
List<String> values = getValueList();
|
||||||
|
if (values == null)
|
||||||
|
return null;
|
||||||
|
return values.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getValueList()
|
||||||
{
|
{
|
||||||
if (_value == null)
|
if (_value == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
QuotedCSV list = new QuotedCSV(false, _value);
|
QuotedCSV list = new QuotedCSV(false, _value);
|
||||||
return list.getValues().toArray(new String[list.size()]);
|
return list.getValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-core</artifactId>
|
||||||
|
<version>12.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>jetty-proxy</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>Jetty Core :: Proxy</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.proxy</bundle-symbolic-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-slf4j-impl</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
|
<artifactId>jetty-http2-server</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-alpn-java-server</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
|
<artifactId>jetty-http2-client-transport</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
module org.eclipse.jetty.proxy
|
||||||
|
{
|
||||||
|
requires org.slf4j;
|
||||||
|
requires transitive org.eclipse.jetty.client;
|
||||||
|
requires transitive org.eclipse.jetty.server;
|
||||||
|
|
||||||
|
exports org.eclipse.jetty.proxy;
|
||||||
|
}
|
|
@ -0,0 +1,717 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.proxy;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
|
import org.eclipse.jetty.io.Content;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Response;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.HttpCookieStore;
|
||||||
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A {@link Handler} that can be used to implement a {@link Forward forward
|
||||||
|
* proxy ("proxy")} or a {@link Reverse reverse proxy ("gateway")} as defined by
|
||||||
|
* <a href="https://datatracker.ietf.org/doc/html/rfc7230#section-2.3">RFC 7230</a>.</p>
|
||||||
|
* <p>This class uses {@link HttpClient} to send requests from the proxy to the server.</p>
|
||||||
|
* <p>The {@code HttpClient} instance is either
|
||||||
|
* {@link #setHttpClient(HttpClient) set explicitly}, or created implicitly.
|
||||||
|
* To customize the implicit {@code HttpClient} instance, applications can
|
||||||
|
* override {@link #newHttpClient()} and {@link #configureHttpClient(HttpClient)}.</p>
|
||||||
|
*
|
||||||
|
* @see Forward
|
||||||
|
* @see Reverse
|
||||||
|
*/
|
||||||
|
public abstract class ProxyHandler extends Handler.Processor
|
||||||
|
{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ProxyHandler.class);
|
||||||
|
private static final String CLIENT_TO_PROXY_REQUEST_ATTRIBUTE = ProxyHandler.class.getName() + ".clientToProxyRequest";
|
||||||
|
private static final EnumSet<HttpHeader> HOP_HEADERS = EnumSet.of(
|
||||||
|
HttpHeader.CONNECTION,
|
||||||
|
HttpHeader.KEEP_ALIVE,
|
||||||
|
HttpHeader.PROXY_AUTHORIZATION,
|
||||||
|
HttpHeader.PROXY_AUTHENTICATE,
|
||||||
|
HttpHeader.PROXY_CONNECTION,
|
||||||
|
HttpHeader.TRANSFER_ENCODING,
|
||||||
|
HttpHeader.TE,
|
||||||
|
HttpHeader.TRAILER,
|
||||||
|
HttpHeader.UPGRADE
|
||||||
|
);
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private String proxyToServerHost;
|
||||||
|
private String viaHost;
|
||||||
|
|
||||||
|
public HttpClient getHttpClient()
|
||||||
|
{
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHttpClient(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the proxy-to-server {@code Host} header value
|
||||||
|
*/
|
||||||
|
public String getProxyToServerHost()
|
||||||
|
{
|
||||||
|
return proxyToServerHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the value to use for the {@code Host} header in proxy-to-server requests.</p>
|
||||||
|
* <p>If {@code null}, the client-to-proxy value is used.</p>
|
||||||
|
*
|
||||||
|
* @param host the proxy-to-server {@code Host} header value
|
||||||
|
*/
|
||||||
|
public void setProxyToServerHost(String host)
|
||||||
|
{
|
||||||
|
this.proxyToServerHost = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value to use for the {@code Via} header
|
||||||
|
*/
|
||||||
|
public String getViaHost()
|
||||||
|
{
|
||||||
|
return viaHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the value to use for the {@code Via} header in proxy-to-server requests.</p>
|
||||||
|
* <p>If {@code null}, the local host name is used.</p>
|
||||||
|
*
|
||||||
|
* @param viaHost the value to use for the {@code Via} header
|
||||||
|
*/
|
||||||
|
public void setViaHost(String viaHost)
|
||||||
|
{
|
||||||
|
this.viaHost = viaHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String viaHost()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return InetAddress.getLocalHost().getHostName();
|
||||||
|
}
|
||||||
|
catch (UnknownHostException x)
|
||||||
|
{
|
||||||
|
return "localhost";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStart() throws Exception
|
||||||
|
{
|
||||||
|
if (httpClient == null)
|
||||||
|
setHttpClient(createHttpClient());
|
||||||
|
addBean(httpClient, true);
|
||||||
|
|
||||||
|
if (viaHost == null)
|
||||||
|
setViaHost(viaHost());
|
||||||
|
|
||||||
|
super.doStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient createHttpClient()
|
||||||
|
{
|
||||||
|
HttpClient httpClient = newHttpClient();
|
||||||
|
configureHttpClient(httpClient);
|
||||||
|
LifeCycle.start(httpClient);
|
||||||
|
httpClient.getContentDecoderFactories().clear();
|
||||||
|
httpClient.getProtocolHandlers().clear();
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Creates a new {@link HttpClient} instance, by default with a thread
|
||||||
|
* pool named {@code proxy-client} and with the
|
||||||
|
* {@link HttpClientTransportDynamic dynamic transport} configured only
|
||||||
|
* with HTTP/1.1.</p>
|
||||||
|
*
|
||||||
|
* @return a new {@code HttpClient} instance
|
||||||
|
*/
|
||||||
|
protected HttpClient newHttpClient()
|
||||||
|
{
|
||||||
|
ClientConnector clientConnector = new ClientConnector();
|
||||||
|
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
|
||||||
|
proxyClientThreads.setName("proxy-client");
|
||||||
|
clientConnector.setExecutor(proxyClientThreads);
|
||||||
|
return new HttpClient(new HttpClientTransportDynamic(clientConnector));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Configures the {@link HttpClient} instance before it is started.</p>
|
||||||
|
*
|
||||||
|
* @param httpClient the {@code HttpClient} instance to configure
|
||||||
|
*/
|
||||||
|
protected void configureHttpClient(HttpClient httpClient)
|
||||||
|
{
|
||||||
|
httpClient.setFollowRedirects(false);
|
||||||
|
httpClient.setCookieStore(new HttpCookieStore.Empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String requestId(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
return String.valueOf(System.identityHashCode(clientToProxyRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(Request clientToProxyRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("""
|
||||||
|
{} received C2P request
|
||||||
|
{}
|
||||||
|
{}""",
|
||||||
|
requestId(clientToProxyRequest),
|
||||||
|
clientToProxyRequest,
|
||||||
|
clientToProxyRequest.getHeaders());
|
||||||
|
|
||||||
|
HttpURI rewritten = rewriteHttpURI(clientToProxyRequest);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} URI rewrite {} => {}", requestId(clientToProxyRequest), clientToProxyRequest.getHttpURI(), rewritten);
|
||||||
|
|
||||||
|
var proxyToServerRequest = newProxyToServerRequest(clientToProxyRequest, rewritten);
|
||||||
|
|
||||||
|
copyRequestHeaders(clientToProxyRequest, proxyToServerRequest);
|
||||||
|
|
||||||
|
addProxyHeaders(clientToProxyRequest, proxyToServerRequest);
|
||||||
|
|
||||||
|
if (hasContent(clientToProxyRequest))
|
||||||
|
{
|
||||||
|
if (expects100Continue(clientToProxyRequest))
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var proxyToServerRequestContent = newProxyToServerRequestContent(clientToProxyRequest, proxyToClientResponse, proxyToServerRequest);
|
||||||
|
proxyToServerRequest.body(proxyToServerRequestContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendProxyToServerRequest(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Rewrites the client-to-proxy request URI to the proxy-to-server request URI.</p>
|
||||||
|
*
|
||||||
|
* @param clientToProxyRequest the client-to-proxy request
|
||||||
|
* @return an {@code HttpURI} for the proxy-to-server request
|
||||||
|
*/
|
||||||
|
protected abstract HttpURI rewriteHttpURI(Request clientToProxyRequest);
|
||||||
|
|
||||||
|
protected org.eclipse.jetty.client.api.Request newProxyToServerRequest(Request clientToProxyRequest, HttpURI newHttpURI)
|
||||||
|
{
|
||||||
|
return getHttpClient().newRequest(newHttpURI.toURI())
|
||||||
|
.method(clientToProxyRequest.getMethod())
|
||||||
|
.attribute(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE, clientToProxyRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void copyRequestHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||||
|
{
|
||||||
|
Set<String> headersToRemove = findConnectionHeaders(clientToProxyRequest);
|
||||||
|
|
||||||
|
for (HttpField clientToProxyRequestField : clientToProxyRequest.getHeaders())
|
||||||
|
{
|
||||||
|
HttpHeader clientToProxyRequestHeader = clientToProxyRequestField.getHeader();
|
||||||
|
|
||||||
|
if (HttpHeader.HOST == clientToProxyRequestHeader)
|
||||||
|
{
|
||||||
|
String host = getProxyToServerHost();
|
||||||
|
if (host != null)
|
||||||
|
{
|
||||||
|
proxyToServerRequest.headers(headers -> headers.put(HttpHeader.HOST, host));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HOP_HEADERS.contains(clientToProxyRequestHeader))
|
||||||
|
continue;
|
||||||
|
if (headersToRemove != null && headersToRemove.contains(clientToProxyRequestField.getLowerCaseName()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
proxyToServerRequest.headers(headers -> headers.add(clientToProxyRequestField));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> findConnectionHeaders(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
// Any header listed by the Connection header must be removed:
|
||||||
|
// http://tools.ietf.org/html/rfc7230#section-6.1.
|
||||||
|
Set<String> hopHeaders = null;
|
||||||
|
List<String> connectionHeaders = clientToProxyRequest.getHeaders().getValuesList(HttpHeader.CONNECTION);
|
||||||
|
for (String value : connectionHeaders)
|
||||||
|
{
|
||||||
|
String[] values = value.split(",");
|
||||||
|
for (String name : values)
|
||||||
|
{
|
||||||
|
name = name.trim().toLowerCase(Locale.ENGLISH);
|
||||||
|
if (hopHeaders == null)
|
||||||
|
hopHeaders = new HashSet<>();
|
||||||
|
hopHeaders.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hopHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addProxyHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||||
|
{
|
||||||
|
addViaHeader(clientToProxyRequest, proxyToServerRequest);
|
||||||
|
addForwardedHeader(clientToProxyRequest, proxyToServerRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addViaHeader(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||||
|
{
|
||||||
|
String protocol = clientToProxyRequest.getConnectionMetaData().getProtocol();
|
||||||
|
String[] parts = protocol.split("/", 2);
|
||||||
|
// Retain only the version if the protocol is HTTP.
|
||||||
|
String protocolPart = parts.length == 2 && "HTTP".equalsIgnoreCase(parts[0]) ? parts[1] : protocol;
|
||||||
|
String viaHeaderValue = protocolPart + " " + getViaHost();
|
||||||
|
proxyToServerRequest.headers(headers -> headers.computeField(HttpHeader.VIA, (header, viaFields) ->
|
||||||
|
{
|
||||||
|
if (viaFields == null || viaFields.isEmpty())
|
||||||
|
return new HttpField(header, viaHeaderValue);
|
||||||
|
String separator = ", ";
|
||||||
|
String newValue = viaFields.stream()
|
||||||
|
.flatMap(field -> Stream.of(field.getValues()))
|
||||||
|
.filter(value -> !StringUtil.isBlank(value))
|
||||||
|
.collect(Collectors.joining(separator));
|
||||||
|
if (newValue.length() > 0)
|
||||||
|
newValue += separator;
|
||||||
|
newValue += viaHeaderValue;
|
||||||
|
return new HttpField(HttpHeader.VIA, newValue);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addForwardedHeader(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||||
|
{
|
||||||
|
String byAttr = Request.getLocalAddr(clientToProxyRequest);
|
||||||
|
String forAttr = Request.getRemoteAddr(clientToProxyRequest);
|
||||||
|
String hostAttr = clientToProxyRequest.getHeaders().get(HttpHeader.HOST);
|
||||||
|
String scheme = clientToProxyRequest.getHttpURI().getScheme();
|
||||||
|
// Even if the request came through a secure channel, look at the original scheme if present.
|
||||||
|
// For example, a client with a forward proxy may want to communicate in clear-text with the
|
||||||
|
// server (so the scheme is http), but securely with the forward proxy (so isSecure() is true).
|
||||||
|
String protoAttr = scheme == null ? (clientToProxyRequest.isSecure() ? "https" : "http") : scheme;
|
||||||
|
String forwardedValue = "by=%s;for=%s;host=%s;proto=%s".formatted(
|
||||||
|
QuotedStringTokenizer.quote(byAttr),
|
||||||
|
QuotedStringTokenizer.quote(forAttr),
|
||||||
|
QuotedStringTokenizer.quote(hostAttr),
|
||||||
|
protoAttr
|
||||||
|
);
|
||||||
|
|
||||||
|
proxyToServerRequest.headers(headers -> headers.computeField(HttpHeader.FORWARDED, (header, fields) ->
|
||||||
|
{
|
||||||
|
String newValue;
|
||||||
|
if (fields == null || fields.isEmpty())
|
||||||
|
{
|
||||||
|
newValue = forwardedValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String separator = ", ";
|
||||||
|
newValue = fields.stream()
|
||||||
|
.flatMap(field -> field.getValueList().stream())
|
||||||
|
.collect(Collectors.joining(separator));
|
||||||
|
newValue += separator + forwardedValue;
|
||||||
|
}
|
||||||
|
return new HttpField(HttpHeader.FORWARDED, newValue);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasContent(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
if (clientToProxyRequest.getLength() > 0)
|
||||||
|
return true;
|
||||||
|
HttpFields headers = clientToProxyRequest.getHeaders();
|
||||||
|
return headers.get(HttpHeader.CONTENT_TYPE) != null ||
|
||||||
|
headers.get(HttpHeader.TRANSFER_ENCODING) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean expects100Continue(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
return HttpHeaderValue.CONTINUE.is(clientToProxyRequest.getHeaders().get(HttpHeader.EXPECT));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected org.eclipse.jetty.client.api.Request.Content newProxyToServerRequestContent(Request clientToProxyRequest, Response proxyToClientResponse, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||||
|
{
|
||||||
|
return new ProxyRequestContent(clientToProxyRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendProxyToServerRequest(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("""
|
||||||
|
{} sending P2S request
|
||||||
|
{}
|
||||||
|
{}""",
|
||||||
|
requestId(clientToProxyRequest),
|
||||||
|
proxyToServerRequest,
|
||||||
|
proxyToServerRequest.getHeaders());
|
||||||
|
}
|
||||||
|
proxyToServerRequest.send(newServerToProxyResponseListener(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected org.eclipse.jetty.client.api.Response.CompleteListener newServerToProxyResponseListener(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||||
|
{
|
||||||
|
return new ProxyResponseListener(clientToProxyRequest, proxyToServerRequest, proxyToClientResponse, proxyToClientCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpField filterServerToProxyResponseField(HttpField serverToProxyResponseField)
|
||||||
|
{
|
||||||
|
return serverToProxyResponseField;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onServerToProxyResponseFailure(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback, Throwable failure)
|
||||||
|
{
|
||||||
|
int status = HttpStatus.BAD_GATEWAY_502;
|
||||||
|
if (failure instanceof TimeoutException)
|
||||||
|
status = HttpStatus.GATEWAY_TIMEOUT_504;
|
||||||
|
Callback callback = new ProxyToClientResponseFailureCallback(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback);
|
||||||
|
Response.writeError(clientToProxyRequest, proxyToClientResponse, callback, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onProxyToClientResponseComplete(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||||
|
{
|
||||||
|
proxyToClientCallback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onProxyToClientResponseFailure(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback, Throwable failure)
|
||||||
|
{
|
||||||
|
// There is no point trying to write an error,
|
||||||
|
// we already know we cannot write to the client.
|
||||||
|
proxyToClientCallback.failed(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A {@code ProxyHandler} that can be used to implement a forward proxy server.</p>
|
||||||
|
* <p>Forward proxies are configured in client applications that use
|
||||||
|
* {@link HttpClient} in this way:</p>
|
||||||
|
* <pre>{@code
|
||||||
|
* httpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* @see org.eclipse.jetty.client.ProxyConfiguration
|
||||||
|
* @see org.eclipse.jetty.client.HttpProxy
|
||||||
|
* @see Reverse
|
||||||
|
*/
|
||||||
|
public static class Forward extends ProxyHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>Applications that use this class should return the client-to-proxy
|
||||||
|
* request URI, since clients will send the absolute URI of the server.</p>
|
||||||
|
*
|
||||||
|
* @param clientToProxyRequest the client-to-proxy request
|
||||||
|
* @return the client-to-proxy request URI
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
return clientToProxyRequest.getHttpURI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A {@code ProxyHandler} that can be used to implement a reverse proxy.</p>
|
||||||
|
* <p>A reverse proxy must rewrite the client-to-proxy request URI into the
|
||||||
|
* proxy-to-server request URI.
|
||||||
|
* This can be done by providing a rewrite function to the constructor,
|
||||||
|
* and/or override {@link #rewriteHttpURI(Request)}.</p>
|
||||||
|
*
|
||||||
|
* @see Forward
|
||||||
|
*/
|
||||||
|
public static class Reverse extends ProxyHandler
|
||||||
|
{
|
||||||
|
private final Function<Request, HttpURI> httpURIRewriter;
|
||||||
|
|
||||||
|
public Reverse(Function<Request, HttpURI> httpURIRewriter)
|
||||||
|
{
|
||||||
|
this.httpURIRewriter = httpURIRewriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function<Request, HttpURI> getHttpURIRewriter()
|
||||||
|
{
|
||||||
|
return httpURIRewriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* <p>Applications that use this class must provide a rewrite function
|
||||||
|
* to the constructor.</p>
|
||||||
|
* <p>The rewrite function rewrites the client-to-proxy request URI,
|
||||||
|
* for example {@code http://example.com/path}, to the proxy-to-server
|
||||||
|
* request URI, for example {@code http://backend1:8080/path}.</p>
|
||||||
|
*
|
||||||
|
* @param clientToProxyRequest the client-to-proxy request
|
||||||
|
* @return the proxy-to-server request URI.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected HttpURI rewriteHttpURI(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
return getHttpURIRewriter().apply(clientToProxyRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class ProxyRequestContent implements org.eclipse.jetty.client.api.Request.Content
|
||||||
|
{
|
||||||
|
private final Request clientToProxyRequest;
|
||||||
|
|
||||||
|
public ProxyRequestContent(Request clientToProxyRequest)
|
||||||
|
{
|
||||||
|
this.clientToProxyRequest = clientToProxyRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLength()
|
||||||
|
{
|
||||||
|
return clientToProxyRequest.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Content.Chunk read()
|
||||||
|
{
|
||||||
|
Content.Chunk chunk = clientToProxyRequest.read();
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} read C2P content {}", requestId(clientToProxyRequest), chunk);
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void demand(Runnable demandCallback)
|
||||||
|
{
|
||||||
|
clientToProxyRequest.demand(demandCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fail(Throwable failure)
|
||||||
|
{
|
||||||
|
clientToProxyRequest.fail(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType()
|
||||||
|
{
|
||||||
|
return clientToProxyRequest.getHeaders().get(HttpHeader.CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean rewind()
|
||||||
|
{
|
||||||
|
// TODO: can this be delegated to the clientToProxyRequest?
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ProxyResponseListener extends Callback.Completable implements org.eclipse.jetty.client.api.Response.Listener
|
||||||
|
{
|
||||||
|
private final Request clientToProxyRequest;
|
||||||
|
private final org.eclipse.jetty.client.api.Request proxyToServerRequest;
|
||||||
|
private final Response proxyToClientResponse;
|
||||||
|
private final Callback proxyToClientCallback;
|
||||||
|
|
||||||
|
public ProxyResponseListener(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||||
|
{
|
||||||
|
this.clientToProxyRequest = clientToProxyRequest;
|
||||||
|
this.proxyToServerRequest = proxyToServerRequest;
|
||||||
|
this.proxyToClientResponse = proxyToClientResponse;
|
||||||
|
this.proxyToClientCallback = proxyToClientCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBegin(org.eclipse.jetty.client.api.Response serverToProxyResponse)
|
||||||
|
{
|
||||||
|
proxyToClientResponse.setStatus(serverToProxyResponse.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(org.eclipse.jetty.client.api.Response serverToProxyResponse)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("""
|
||||||
|
{} received S2P response
|
||||||
|
{}
|
||||||
|
{}""",
|
||||||
|
requestId(clientToProxyRequest),
|
||||||
|
serverToProxyResponse,
|
||||||
|
serverToProxyResponse.getHeaders());
|
||||||
|
}
|
||||||
|
for (HttpField serverToProxyResponseField : serverToProxyResponse.getHeaders())
|
||||||
|
{
|
||||||
|
if (HOP_HEADERS.contains(serverToProxyResponseField.getHeader()))
|
||||||
|
continue;
|
||||||
|
HttpField newField = filterServerToProxyResponseField(serverToProxyResponseField);
|
||||||
|
if (newField == null)
|
||||||
|
continue;
|
||||||
|
proxyToClientResponse.getHeaders().add(newField);
|
||||||
|
}
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOG.debug("""
|
||||||
|
{} sending P2C response
|
||||||
|
{}
|
||||||
|
{}""",
|
||||||
|
requestId(clientToProxyRequest),
|
||||||
|
proxyToClientResponse,
|
||||||
|
proxyToClientResponse.getHeaders());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(org.eclipse.jetty.client.api.Response serverToProxyResponse, ByteBuffer serverToProxyContent, Callback serverToProxyContentCallback)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} received S2P content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||||
|
Callback callback = new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} succeeded to write P2C content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||||
|
serverToProxyContentCallback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable failure)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} failed to write P2C content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent), failure);
|
||||||
|
serverToProxyContentCallback.failed(failure);
|
||||||
|
// Cannot write towards the client, abort towards the server.
|
||||||
|
serverToProxyResponse.abort(failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InvocationType getInvocationType()
|
||||||
|
{
|
||||||
|
return InvocationType.NON_BLOCKING;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
proxyToClientResponse.write(false, serverToProxyContent, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(org.eclipse.jetty.client.api.Response serverToProxyResponse)
|
||||||
|
{
|
||||||
|
proxyToClientResponse.write(true, BufferUtil.EMPTY_BUFFER, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result)
|
||||||
|
{
|
||||||
|
if (result.isSucceeded())
|
||||||
|
{
|
||||||
|
// Wait for the last write to complete.
|
||||||
|
whenComplete((r, failure) ->
|
||||||
|
{
|
||||||
|
if (failure == null)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} P2C response complete {}", requestId(clientToProxyRequest), proxyToClientResponse);
|
||||||
|
onProxyToClientResponseComplete(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} P2C response failure {}", requestId(clientToProxyRequest), proxyToClientResponse, failure);
|
||||||
|
onProxyToClientResponseFailure(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback, failure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} S2P failure {}", requestId(clientToProxyRequest), result.getResponse(), result.getFailure());
|
||||||
|
onServerToProxyResponseFailure(clientToProxyRequest, proxyToServerRequest, result.getResponse(), proxyToClientResponse, proxyToClientCallback, result.getFailure());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProxyToClientResponseFailureCallback implements Callback
|
||||||
|
{
|
||||||
|
private final Request clientToProxyRequest;
|
||||||
|
private final org.eclipse.jetty.client.api.Request proxyToServerRequest;
|
||||||
|
private final org.eclipse.jetty.client.api.Response serverToProxyResponse;
|
||||||
|
private final Response proxyToClientResponse;
|
||||||
|
private final Callback proxyToClientCallback;
|
||||||
|
|
||||||
|
private ProxyToClientResponseFailureCallback(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||||
|
{
|
||||||
|
this.clientToProxyRequest = clientToProxyRequest;
|
||||||
|
this.proxyToServerRequest = proxyToServerRequest;
|
||||||
|
this.serverToProxyResponse = serverToProxyResponse;
|
||||||
|
this.proxyToClientResponse = proxyToClientResponse;
|
||||||
|
this.proxyToClientCallback = proxyToClientCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} P2C response complete {}", requestId(clientToProxyRequest), proxyToClientResponse);
|
||||||
|
onProxyToClientResponseComplete(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} P2C response failure {}", requestId(clientToProxyRequest), proxyToClientResponse, x);
|
||||||
|
onProxyToClientResponseFailure(clientToProxyRequest, proxyToServerRequest, serverToProxyResponse, proxyToClientResponse, proxyToClientCallback, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InvocationType getInvocationType()
|
||||||
|
{
|
||||||
|
return InvocationType.NON_BLOCKING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,591 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.proxy;
|
||||||
|
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.client.AbstractConnectionPool;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.HttpProxy;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Destination;
|
||||||
|
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||||
|
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http.HostPortHttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.http.MetaData;
|
||||||
|
import org.eclipse.jetty.http2.HTTP2Cipher;
|
||||||
|
import org.eclipse.jetty.http2.api.Session;
|
||||||
|
import org.eclipse.jetty.http2.api.Stream;
|
||||||
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
|
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
|
||||||
|
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||||
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
|
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||||
|
import org.eclipse.jetty.http2.internal.ErrorCode;
|
||||||
|
import org.eclipse.jetty.http2.internal.HTTP2Connection;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Response;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.FuturePromise;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class ForwardProxyWithDynamicTransportTest
|
||||||
|
{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ForwardProxyWithDynamicTransportTest.class);
|
||||||
|
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector serverConnector;
|
||||||
|
private ServerConnector serverTLSConnector;
|
||||||
|
private Server proxy;
|
||||||
|
private ServerConnector proxyConnector;
|
||||||
|
private ServerConnector proxyTLSConnector;
|
||||||
|
private HTTP2Client http2Client;
|
||||||
|
private HttpClient client;
|
||||||
|
|
||||||
|
private void start(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
startServer(handler);
|
||||||
|
startProxy(new ConnectHandler());
|
||||||
|
startClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startServer(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
||||||
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
sslContextFactory.setUseCipherSuitesOrder(true);
|
||||||
|
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
|
||||||
|
|
||||||
|
QueuedThreadPool serverThreads = new QueuedThreadPool();
|
||||||
|
serverThreads.setName("server");
|
||||||
|
server = new Server(serverThreads);
|
||||||
|
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
HttpConnectionFactory h1c = new HttpConnectionFactory(httpConfig);
|
||||||
|
HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
|
||||||
|
serverConnector = new ServerConnector(server, 1, 1, h1c, h2c);
|
||||||
|
server.addConnector(serverConnector);
|
||||||
|
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
|
||||||
|
httpsConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory h1 = new HttpConnectionFactory(httpsConfig);
|
||||||
|
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig);
|
||||||
|
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
|
||||||
|
alpn.setDefaultProtocol(h1.getProtocol());
|
||||||
|
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
|
||||||
|
serverTLSConnector = new ServerConnector(server, 1, 1, ssl, alpn, h2, h1, h2c);
|
||||||
|
server.addConnector(serverTLSConnector);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
LOG.info("Started server on :{} and :{}", serverConnector.getLocalPort(), serverTLSConnector.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startProxy(ConnectHandler connectHandler) throws Exception
|
||||||
|
{
|
||||||
|
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
|
||||||
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
sslContextFactory.setUseCipherSuitesOrder(true);
|
||||||
|
sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
|
||||||
|
|
||||||
|
QueuedThreadPool proxyThreads = new QueuedThreadPool();
|
||||||
|
proxyThreads.setName("proxy");
|
||||||
|
proxy = new Server(proxyThreads);
|
||||||
|
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
ConnectionFactory h1c = new HttpConnectionFactory(httpConfig);
|
||||||
|
ConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
|
||||||
|
proxyConnector = new ServerConnector(proxy, 1, 1, h1c, h2c);
|
||||||
|
proxy.addConnector(proxyConnector);
|
||||||
|
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
|
||||||
|
httpsConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
HttpConnectionFactory h1 = new HttpConnectionFactory(httpsConfig);
|
||||||
|
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpsConfig);
|
||||||
|
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
|
||||||
|
alpn.setDefaultProtocol(h1.getProtocol());
|
||||||
|
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
|
||||||
|
proxyTLSConnector = new ServerConnector(proxy, 1, 1, ssl, alpn, h2, h1, h2c);
|
||||||
|
proxy.addConnector(proxyTLSConnector);
|
||||||
|
proxy.setHandler(connectHandler);
|
||||||
|
connectHandler.setHandler(new ProxyHandler.Forward()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected HttpClient newHttpClient()
|
||||||
|
{
|
||||||
|
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
|
||||||
|
proxyClientThreads.setName("proxy-client");
|
||||||
|
ClientConnector proxyClientConnector = new ClientConnector();
|
||||||
|
proxyClientConnector.setSelectors(1);
|
||||||
|
proxyClientConnector.setExecutor(proxyClientThreads);
|
||||||
|
proxyClientConnector.setSslContextFactory(new SslContextFactory.Client(true));
|
||||||
|
HTTP2Client proxyHTTP2Client = new HTTP2Client(proxyClientConnector);
|
||||||
|
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
|
||||||
|
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(proxyHTTP2Client);
|
||||||
|
return new HttpClient(new HttpClientTransportDynamic(proxyClientConnector, h1, http2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proxy.start();
|
||||||
|
LOG.info("Started proxy on :{} and :{}", proxyConnector.getLocalPort(), proxyTLSConnector.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startClient() throws Exception
|
||||||
|
{
|
||||||
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
|
clientThreads.setName("client");
|
||||||
|
ClientConnector clientConnector = new ClientConnector();
|
||||||
|
clientConnector.setSelectors(1);
|
||||||
|
clientConnector.setExecutor(clientThreads);
|
||||||
|
clientConnector.setSslContextFactory(new SslContextFactory.Client(true));
|
||||||
|
http2Client = new HTTP2Client(clientConnector);
|
||||||
|
ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
|
||||||
|
ClientConnectionFactory.Info http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
|
||||||
|
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, h1, http2));
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
LifeCycle.stop(client);
|
||||||
|
LifeCycle.stop(proxy);
|
||||||
|
LifeCycle.stop(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static java.util.stream.Stream<Arguments> proxyMatrix()
|
||||||
|
{
|
||||||
|
var h1 = List.of("http/1.1");
|
||||||
|
var h2c = List.of("h2c");
|
||||||
|
var h2 = List.of("h2");
|
||||||
|
return java.util.stream.Stream.of(
|
||||||
|
// HTTP/1.1 Proxy with HTTP/1.1 Server.
|
||||||
|
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 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.
|
||||||
|
// TODO: re-enable when HTTP/2 tunnel support is implemented
|
||||||
|
// 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.
|
||||||
|
// TODO: re-enable when HTTP/2 tunnel support is implemented
|
||||||
|
// 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("proxyMatrix")
|
||||||
|
public void testProxy(Origin.Protocol proxyProtocol, boolean proxySecure, HttpVersion serverProtocol, boolean serverSecure) throws Exception
|
||||||
|
{
|
||||||
|
int status = HttpStatus.NO_CONTENT_204;
|
||||||
|
start(new Handler.Processor()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void process(Request request, Response response, Callback callback)
|
||||||
|
{
|
||||||
|
response.setStatus(status);
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int proxyPort = proxySecure ? proxyTLSConnector.getLocalPort() : proxyConnector.getLocalPort();
|
||||||
|
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||||
|
HttpProxy proxy = new HttpProxy(proxyAddress, proxySecure, proxyProtocol);
|
||||||
|
client.getProxyConfiguration().getProxies().add(proxy);
|
||||||
|
|
||||||
|
String scheme = serverSecure ? "https" : "http";
|
||||||
|
int serverPort = serverSecure ? serverTLSConnector.getLocalPort() : serverConnector.getLocalPort();
|
||||||
|
ContentResponse response1 = client.newRequest("localhost", serverPort)
|
||||||
|
.scheme(scheme)
|
||||||
|
.version(serverProtocol)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
assertEquals(status, response1.getStatus());
|
||||||
|
|
||||||
|
// Make a second request to be sure it went through the same connection.
|
||||||
|
ContentResponse response2 = client.newRequest("localhost", serverPort)
|
||||||
|
.scheme(scheme)
|
||||||
|
.version(serverProtocol)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
assertEquals(status, response2.getStatus());
|
||||||
|
|
||||||
|
List<Destination> destinations = client.getDestinations().stream()
|
||||||
|
.filter(d -> d.getPort() == serverPort)
|
||||||
|
.toList();
|
||||||
|
assertEquals(1, destinations.size());
|
||||||
|
HttpDestination destination = (HttpDestination)destinations.get(0);
|
||||||
|
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
|
||||||
|
assertEquals(1, connectionPool.getConnectionCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
|
||||||
|
public void testHTTP2TunnelClosedByClient() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
int proxyPort = proxyConnector.getLocalPort();
|
||||||
|
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||||
|
HttpProxy proxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
|
||||||
|
client.getProxyConfiguration().getProxies().add(proxy);
|
||||||
|
|
||||||
|
long idleTimeout = 1000;
|
||||||
|
http2Client.setStreamIdleTimeout(idleTimeout);
|
||||||
|
|
||||||
|
String serverScheme = "http";
|
||||||
|
int serverPort = serverConnector.getLocalPort();
|
||||||
|
ContentResponse response = client.newRequest("localhost", serverPort)
|
||||||
|
.scheme(serverScheme)
|
||||||
|
.version(HttpVersion.HTTP_1_1)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||||
|
|
||||||
|
// Client will close the HTTP2StreamEndPoint.
|
||||||
|
Thread.sleep(2 * idleTimeout);
|
||||||
|
|
||||||
|
List<Destination> destinations = client.getDestinations().stream()
|
||||||
|
.filter(d -> d.getPort() == serverPort)
|
||||||
|
.toList();
|
||||||
|
assertEquals(1, destinations.size());
|
||||||
|
HttpDestination destination = (HttpDestination)destinations.get(0);
|
||||||
|
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
|
||||||
|
assertEquals(0, connectionPool.getConnectionCount());
|
||||||
|
|
||||||
|
List<HTTP2Connection> serverConnections = proxyConnector.getConnectedEndPoints().stream()
|
||||||
|
.map(EndPoint::getConnection)
|
||||||
|
.map(HTTP2Connection.class::cast)
|
||||||
|
.toList();
|
||||||
|
assertEquals(1, serverConnections.size());
|
||||||
|
assertTrue(serverConnections.get(0).getSession().getStreams().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProxyDown() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
int proxyPort = proxyConnector.getLocalPort();
|
||||||
|
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||||
|
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
|
||||||
|
client.getProxyConfiguration().getProxies().add(httpProxy);
|
||||||
|
proxy.stop();
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
|
.version(HttpVersion.HTTP_1_1)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send(result ->
|
||||||
|
{
|
||||||
|
assertTrue(result.isFailed());
|
||||||
|
assertThat(result.getFailure(), Matchers.instanceOf(ConnectException.class));
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
|
||||||
|
public void testHTTP2TunnelHardClosedByProxy() throws Exception
|
||||||
|
{
|
||||||
|
startServer(new EmptyServerHandler());
|
||||||
|
CountDownLatch closeLatch = new CountDownLatch(1);
|
||||||
|
startProxy(new ConnectHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
|
||||||
|
{
|
||||||
|
request.getConnectionMetaData().getConnection().getEndPoint().close();
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
startClient();
|
||||||
|
|
||||||
|
int proxyPort = proxyConnector.getLocalPort();
|
||||||
|
Origin.Address proxyAddress = new Origin.Address("localhost", proxyPort);
|
||||||
|
HttpProxy httpProxy = new HttpProxy(proxyAddress, false, new Origin.Protocol(List.of("h2c"), false));
|
||||||
|
client.getProxyConfiguration().getProxies().add(httpProxy);
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||||
|
.version(HttpVersion.HTTP_1_1)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send(result ->
|
||||||
|
{
|
||||||
|
assertTrue(result.isFailed());
|
||||||
|
assertThat(result.getFailure(), Matchers.instanceOf(ClosedChannelException.class));
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
List<Destination> destinations = client.getDestinations().stream()
|
||||||
|
.filter(d -> d.getPort() == proxyPort)
|
||||||
|
.toList();
|
||||||
|
assertEquals(1, destinations.size());
|
||||||
|
HttpDestination destination = (HttpDestination)destinations.get(0);
|
||||||
|
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
|
||||||
|
assertEquals(0, connectionPool.getConnectionCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
|
||||||
|
public void testHTTP2TunnelResetByClient() throws Exception
|
||||||
|
{
|
||||||
|
startServer(new EmptyServerHandler());
|
||||||
|
CountDownLatch closeLatch = new CountDownLatch(2);
|
||||||
|
startProxy(new ConnectHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context)
|
||||||
|
{
|
||||||
|
return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void close(Throwable failure)
|
||||||
|
{
|
||||||
|
super.close(failure);
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
|
||||||
|
{
|
||||||
|
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void close(Throwable failure)
|
||||||
|
{
|
||||||
|
super.close(failure);
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
startClient();
|
||||||
|
|
||||||
|
FuturePromise<Session> sessionPromise = new FuturePromise<>();
|
||||||
|
http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener() {}, sessionPromise);
|
||||||
|
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
|
||||||
|
String serverAddress = "localhost:" + serverConnector.getLocalPort();
|
||||||
|
MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(serverAddress), null, HttpFields.EMPTY, null);
|
||||||
|
HeadersFrame frame = new HeadersFrame(connect, null, false);
|
||||||
|
FuturePromise<Stream> streamPromise = new FuturePromise<>();
|
||||||
|
CountDownLatch tunnelLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||||
|
session.newStream(frame, streamPromise, new Stream.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||||
|
{
|
||||||
|
MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
||||||
|
if (response.getStatus() == HttpStatus.OK_200)
|
||||||
|
tunnelLatch.countDown();
|
||||||
|
stream.demand();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable(Stream stream)
|
||||||
|
{
|
||||||
|
Stream.Data data = stream.readData();
|
||||||
|
String response = BufferUtil.toString(data.frame().getData(), StandardCharsets.UTF_8);
|
||||||
|
data.release();
|
||||||
|
if (response.startsWith("HTTP/1.1 200"))
|
||||||
|
responseLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
|
||||||
|
assertTrue(tunnelLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Tunnel is established, send a HTTP/1.1 request.
|
||||||
|
String h1 = "GET / HTTP/1.1\r\n" +
|
||||||
|
"Host: " + serverAddress + "\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(h1.getBytes(StandardCharsets.UTF_8)), false), Callback.NOOP);
|
||||||
|
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Now reset the stream, tunnel must be closed.
|
||||||
|
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
||||||
|
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("re-enable when HTTP/2 tunnel support is implemented")
|
||||||
|
public void testHTTP2TunnelProxyStreamTimeout() throws Exception
|
||||||
|
{
|
||||||
|
startServer(new EmptyServerHandler());
|
||||||
|
CountDownLatch closeLatch = new CountDownLatch(2);
|
||||||
|
startProxy(new ConnectHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context)
|
||||||
|
{
|
||||||
|
return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void close(Throwable failure)
|
||||||
|
{
|
||||||
|
super.close(failure);
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
|
||||||
|
{
|
||||||
|
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void close(Throwable failure)
|
||||||
|
{
|
||||||
|
super.close(failure);
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
startClient();
|
||||||
|
|
||||||
|
long streamIdleTimeout = 1000;
|
||||||
|
ConnectionFactory h2c = proxyConnector.getConnectionFactory("h2c");
|
||||||
|
((HTTP2CServerConnectionFactory)h2c).setStreamIdleTimeout(streamIdleTimeout);
|
||||||
|
|
||||||
|
FuturePromise<Session> sessionPromise = new FuturePromise<>();
|
||||||
|
http2Client.connect(new InetSocketAddress("localhost", proxyConnector.getLocalPort()), new Session.Listener() {}, sessionPromise);
|
||||||
|
Session session = sessionPromise.get(5, TimeUnit.SECONDS);
|
||||||
|
String serverAddress = "localhost:" + serverConnector.getLocalPort();
|
||||||
|
MetaData.ConnectRequest connect = new MetaData.ConnectRequest(HttpScheme.HTTP, new HostPortHttpField(serverAddress), null, HttpFields.EMPTY, null);
|
||||||
|
HeadersFrame frame = new HeadersFrame(connect, null, false);
|
||||||
|
FuturePromise<Stream> streamPromise = new FuturePromise<>();
|
||||||
|
CountDownLatch tunnelLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||||
|
CountDownLatch resetLatch = new CountDownLatch(1);
|
||||||
|
session.newStream(frame, streamPromise, new Stream.Listener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||||
|
{
|
||||||
|
MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
||||||
|
if (response.getStatus() == HttpStatus.OK_200)
|
||||||
|
tunnelLatch.countDown();
|
||||||
|
stream.demand();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable(Stream stream)
|
||||||
|
{
|
||||||
|
Stream.Data data = stream.readData();
|
||||||
|
String response = BufferUtil.toString(data.frame().getData(), StandardCharsets.UTF_8);
|
||||||
|
data.release();
|
||||||
|
if (response.startsWith("HTTP/1.1 200"))
|
||||||
|
responseLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReset(Stream stream, ResetFrame frame, Callback callback)
|
||||||
|
{
|
||||||
|
resetLatch.countDown();
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
|
||||||
|
assertTrue(tunnelLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Tunnel is established, send a HTTP/1.1 request.
|
||||||
|
String h1 = "GET / HTTP/1.1\r\n" +
|
||||||
|
"Host: " + serverAddress + "\r\n" +
|
||||||
|
"\r\n";
|
||||||
|
stream.data(new DataFrame(stream.getId(), ByteBuffer.wrap(h1.getBytes(StandardCharsets.UTF_8)), false), Callback.NOOP);
|
||||||
|
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Wait until the proxy stream idle times out.
|
||||||
|
Thread.sleep(2 * streamIdleTimeout);
|
||||||
|
|
||||||
|
// Client should see a RST_STREAM.
|
||||||
|
assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
// Tunnel must be closed.
|
||||||
|
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EmptyServerHandler extends Handler.Processor
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void process(Request request, Response response, Callback callback)
|
||||||
|
{
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,151 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.proxy;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||||
|
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
|
||||||
|
import org.eclipse.jetty.client.util.StringRequestContent;
|
||||||
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
|
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
|
import org.eclipse.jetty.io.Content;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Response;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class ReverseProxyTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private ServerConnector serverConnector;
|
||||||
|
private Server proxy;
|
||||||
|
private ServerConnector proxyConnector;
|
||||||
|
private HttpClient client;
|
||||||
|
|
||||||
|
private void startServer(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
QueuedThreadPool serverPool = new QueuedThreadPool();
|
||||||
|
serverPool.setName("server");
|
||||||
|
server = new Server(serverPool);
|
||||||
|
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
serverConnector = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig), new HTTP2CServerConnectionFactory(httpConfig));
|
||||||
|
server.addConnector(serverConnector);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startProxy(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
QueuedThreadPool proxyPool = new QueuedThreadPool();
|
||||||
|
proxyPool.setName("proxy");
|
||||||
|
proxy = new Server(proxyPool);
|
||||||
|
HttpConfiguration configuration = new HttpConfiguration();
|
||||||
|
configuration.setSendDateHeader(false);
|
||||||
|
configuration.setSendServerVersion(false);
|
||||||
|
proxyConnector = new ServerConnector(proxy, 1, 1, new HttpConnectionFactory(configuration), new HTTP2CServerConnectionFactory(configuration));
|
||||||
|
proxy.addConnector(proxyConnector);
|
||||||
|
proxy.setHandler(handler);
|
||||||
|
proxy.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startClient() throws Exception
|
||||||
|
{
|
||||||
|
ClientConnector clientConnector = new ClientConnector();
|
||||||
|
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||||
|
clientThreads.setName("client");
|
||||||
|
clientConnector.setExecutor(clientThreads);
|
||||||
|
HTTP2Client http2Client = new HTTP2Client(clientConnector);
|
||||||
|
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, HttpClientConnectionFactory.HTTP11, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
LifeCycle.stop(client);
|
||||||
|
LifeCycle.stop(proxy);
|
||||||
|
LifeCycle.stop(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<HttpVersion> httpVersions()
|
||||||
|
{
|
||||||
|
return List.of(HttpVersion.HTTP_1_1, HttpVersion.HTTP_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("httpVersions")
|
||||||
|
public void testSimple(HttpVersion httpVersion) throws Exception
|
||||||
|
{
|
||||||
|
String clientContent = "hello";
|
||||||
|
String serverContent = "world";
|
||||||
|
startServer(new Handler.Processor()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void process(Request request, Response response, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
String requestContent = Content.Source.asString(request);
|
||||||
|
assertEquals(clientContent, requestContent);
|
||||||
|
Content.Sink.write(response, true, serverContent, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
startProxy(new ProxyHandler.Reverse(clientToProxyRequest ->
|
||||||
|
HttpURI.build(clientToProxyRequest.getHttpURI()).port(serverConnector.getLocalPort()))
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected HttpClient newHttpClient()
|
||||||
|
{
|
||||||
|
ClientConnector proxyClientConnector = new ClientConnector();
|
||||||
|
QueuedThreadPool proxyClientThreads = new QueuedThreadPool();
|
||||||
|
proxyClientThreads.setName("proxy-client");
|
||||||
|
proxyClientConnector.setExecutor(proxyClientThreads);
|
||||||
|
HTTP2Client proxyHTTP2Client = new HTTP2Client(proxyClientConnector);
|
||||||
|
return new HttpClient(new HttpClientTransportDynamic(proxyClientConnector, HttpClientConnectionFactory.HTTP11, new ClientConnectionFactoryOverHTTP2.HTTP2(proxyHTTP2Client)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected org.eclipse.jetty.client.api.Request newProxyToServerRequest(Request clientToProxyRequest, HttpURI newHttpURI)
|
||||||
|
{
|
||||||
|
return super.newProxyToServerRequest(clientToProxyRequest, newHttpURI)
|
||||||
|
.version(httpVersion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
startClient();
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
||||||
|
.version(httpVersion)
|
||||||
|
.body(new StringRequestContent(clientContent))
|
||||||
|
.send();
|
||||||
|
assertEquals(serverContent, response.getContentAsString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
#org.eclipse.jetty.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.proxy.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.server.handler.LEVEL=DEBUG
|
Binary file not shown.
|
@ -27,8 +27,6 @@ import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
|
||||||
import org.eclipse.jetty.http.HttpURI;
|
|
||||||
import org.eclipse.jetty.http.PreEncodedHttpField;
|
import org.eclipse.jetty.http.PreEncodedHttpField;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||||
|
@ -47,12 +45,35 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(SecureRequestCustomizer.class);
|
/**
|
||||||
public static final String X509_CERT = "org.eclipse.jetty.server.x509_cert";
|
* <p>The request attribute name to use to obtain the peer certificate
|
||||||
public static final String CERTIFICATES = "org.eclipse.jetty.server.certificates";
|
* chain as an array of {@link X509Certificate} objects.</p>
|
||||||
private String _sslSessionAttribute = "org.eclipse.jetty.servlet.request.ssl_session"; // TODO better name?
|
*/
|
||||||
private String _sslSessionDataAttribute = _sslSessionAttribute + "_data"; // TODO better name?
|
public static final String PEER_CERTIFICATES_ATTRIBUTE = "org.eclipse.jetty.server.certificates";
|
||||||
|
/**
|
||||||
|
* <p>The request attribute name to use to obtain the local certificate
|
||||||
|
* as an {@link X509} object.</p>
|
||||||
|
*/
|
||||||
|
public static final String X509_ATTRIBUTE = "org.eclipse.jetty.server.x509";
|
||||||
|
/**
|
||||||
|
* <p>The default value of the request attribute name to use to obtain
|
||||||
|
* the {@link SSLSession} object.</p>
|
||||||
|
*
|
||||||
|
* @see #setSslSessionAttribute(String)
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_SSL_SESSION_ATTRIBUTE = "org.eclipse.jetty.server.sslSession";
|
||||||
|
/**
|
||||||
|
* <p>The default value of the request attribute name to use to obtain
|
||||||
|
* the {@link SslSessionData} object.</p>
|
||||||
|
*
|
||||||
|
* @see #getSslSessionDataAttribute()
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_SSL_SESSION_DATA_ATTRIBUTE = newSslSessionDataAttribute(DEFAULT_SSL_SESSION_ATTRIBUTE);
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SecureRequestCustomizer.class);
|
||||||
|
|
||||||
|
private String _sslSessionAttribute = DEFAULT_SSL_SESSION_ATTRIBUTE;
|
||||||
|
private String _sslSessionDataAttribute = DEFAULT_SSL_SESSION_DATA_ATTRIBUTE;
|
||||||
private boolean _sniRequired;
|
private boolean _sniRequired;
|
||||||
private boolean _sniHostCheck;
|
private boolean _sniHostCheck;
|
||||||
private long _stsMaxAge;
|
private long _stsMaxAge;
|
||||||
|
@ -144,21 +165,21 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the Strict-Transport-Security max age.
|
* Sets the Strict-Transport-Security max age in seconds.
|
||||||
*
|
*
|
||||||
* @param stsMaxAgeSeconds The max age in seconds for a Strict-Transport-Security response header. If set less than zero then no header is sent.
|
* @param stsMaxAgeSeconds the max age in seconds for the Strict-Transport-Security response header.
|
||||||
|
* If less than zero then no Strict-Transport-Security response header is set.
|
||||||
*/
|
*/
|
||||||
public void setStsMaxAge(long stsMaxAgeSeconds)
|
public void setStsMaxAge(long stsMaxAgeSeconds)
|
||||||
{
|
{
|
||||||
_stsMaxAge = stsMaxAgeSeconds;
|
setStsMaxAge(stsMaxAgeSeconds, TimeUnit.SECONDS);
|
||||||
formatSTS();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to call {@link #setStsMaxAge(long)}
|
* Sets the Strict-Transport-Security max age in the given time unit.
|
||||||
*
|
*
|
||||||
* @param period The period in units
|
* @param period The max age value
|
||||||
* @param units The {@link TimeUnit} of the period
|
* @param units The {@link TimeUnit} of the max age
|
||||||
*/
|
*/
|
||||||
public void setStsMaxAge(long period, TimeUnit units)
|
public void setStsMaxAge(long period, TimeUnit units)
|
||||||
{
|
{
|
||||||
|
@ -167,7 +188,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if a include subdomain property is sent with any Strict-Transport-Security header
|
* @return whether the {@code includeSubdomains} attribute is sent with the Strict-Transport-Security response header
|
||||||
*/
|
*/
|
||||||
public boolean isStsIncludeSubDomains()
|
public boolean isStsIncludeSubDomains()
|
||||||
{
|
{
|
||||||
|
@ -175,7 +196,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param stsIncludeSubDomains If true, a include subdomain property is sent with any Strict-Transport-Security header
|
* @param stsIncludeSubDomains whether the {@code includeSubdomains} attribute is sent with the Strict-Transport-Security response header
|
||||||
*/
|
*/
|
||||||
public void setStsIncludeSubDomains(boolean stsIncludeSubDomains)
|
public void setStsIncludeSubDomains(boolean stsIncludeSubDomains)
|
||||||
{
|
{
|
||||||
|
@ -185,49 +206,41 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
|
|
||||||
private void formatSTS()
|
private void formatSTS()
|
||||||
{
|
{
|
||||||
if (_stsMaxAge < 0)
|
long stsMaxAge = getStsMaxAge();
|
||||||
|
if (stsMaxAge < 0)
|
||||||
_stsField = null;
|
_stsField = null;
|
||||||
else
|
else
|
||||||
_stsField = new PreEncodedHttpField(HttpHeader.STRICT_TRANSPORT_SECURITY, String.format("max-age=%d%s", _stsMaxAge, _stsIncludeSubDomains ? "; includeSubDomains" : ""));
|
_stsField = new PreEncodedHttpField(HttpHeader.STRICT_TRANSPORT_SECURITY, String.format("max-age=%d%s", stsMaxAge, isStsIncludeSubDomains() ? "; includeSubDomains" : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Request customize(Request request, HttpFields.Mutable responseHeaders)
|
public Request customize(Request request, HttpFields.Mutable responseHeaders)
|
||||||
{
|
{
|
||||||
EndPoint endp = request.getConnectionMetaData().getConnection().getEndPoint();
|
EndPoint endPoint = request.getConnectionMetaData().getConnection().getEndPoint();
|
||||||
HttpURI uri = request.getHttpURI();
|
if (endPoint instanceof DecryptedEndPoint sslEndPoint)
|
||||||
SSLEngine sslEngine;
|
|
||||||
if (endp instanceof DecryptedEndPoint)
|
|
||||||
{
|
{
|
||||||
DecryptedEndPoint sslEndp = (DecryptedEndPoint)endp;
|
SslConnection sslConnection = sslEndPoint.getSslConnection();
|
||||||
SslConnection sslConnection = sslEndp.getSslConnection();
|
SSLEngine sslEngine = sslConnection.getSSLEngine();
|
||||||
uri = (HttpScheme.HTTPS.is(uri.getScheme()))
|
request = newSecureRequest(request, sslEngine);
|
||||||
? uri : HttpURI.build(uri).scheme(HttpScheme.HTTPS);
|
|
||||||
sslEngine = sslConnection.getSSLEngine();
|
|
||||||
}
|
}
|
||||||
else if (endp instanceof ProxyConnectionFactory.ProxyEndPoint)
|
else if (endPoint instanceof ProxyConnectionFactory.ProxyEndPoint proxyEndPoint)
|
||||||
{
|
{
|
||||||
ProxyConnectionFactory.ProxyEndPoint proxy = (ProxyConnectionFactory.ProxyEndPoint)endp;
|
if (proxyEndPoint.getAttribute(ProxyConnectionFactory.TLS_VERSION) != null)
|
||||||
if (proxy.getAttribute(ProxyConnectionFactory.TLS_VERSION) == null)
|
request = newSecureRequest(request, null);
|
||||||
return request;
|
|
||||||
uri = (HttpScheme.HTTPS.is(uri.getScheme()))
|
|
||||||
? uri : HttpURI.build(uri).scheme(HttpScheme.HTTPS);
|
|
||||||
sslEngine = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_stsField != null)
|
if (_stsField != null)
|
||||||
responseHeaders.add(_stsField);
|
responseHeaders.add(_stsField);
|
||||||
|
|
||||||
return newSecureRequest(request, uri, sslEngine);
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SecureRequest newSecureRequest(Request request, HttpURI uri, SSLEngine sslEngine)
|
protected Request newSecureRequest(Request request, SSLEngine sslEngine)
|
||||||
{
|
{
|
||||||
return new SecureRequest(request, uri, sslEngine);
|
if (sslEngine != null)
|
||||||
|
return new SecureRequestWithTLSData(request, sslEngine);
|
||||||
|
else
|
||||||
|
return new SecureRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private X509Certificate[] getCertChain(Connector connector, SSLSession sslSession)
|
private X509Certificate[] getCertChain(Connector connector, SSLSession sslSession)
|
||||||
|
@ -249,7 +262,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
{
|
{
|
||||||
Objects.requireNonNull(attribute);
|
Objects.requireNonNull(attribute);
|
||||||
_sslSessionAttribute = attribute;
|
_sslSessionAttribute = attribute;
|
||||||
_sslSessionDataAttribute = attribute + "_data";
|
_sslSessionDataAttribute = newSslSessionDataAttribute(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSslSessionAttribute()
|
public String getSslSessionAttribute()
|
||||||
|
@ -258,14 +271,16 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get data belonging to the {@link SSLSession}.
|
* @return {@link #getSslSessionAttribute()} {@code + "Data"}
|
||||||
*
|
|
||||||
* @return the SslSessionData
|
|
||||||
*/
|
*/
|
||||||
public static SslSessionData getSslSessionData(SSLSession session)
|
public String getSslSessionDataAttribute()
|
||||||
{
|
{
|
||||||
String key = SslSessionData.class.getName();
|
return _sslSessionDataAttribute;
|
||||||
return (SslSessionData)session.getValue(key);
|
}
|
||||||
|
|
||||||
|
private static String newSslSessionDataAttribute(String sslSessionAttribute)
|
||||||
|
{
|
||||||
|
return sslSessionAttribute + "Data";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void checkSni(Request request, SSLSession session)
|
protected void checkSni(Request request, SSLSession session)
|
||||||
|
@ -291,14 +306,14 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
|
|
||||||
private X509 getX509(SSLSession session)
|
private X509 getX509(SSLSession session)
|
||||||
{
|
{
|
||||||
X509 x509 = (X509)session.getValue(X509_CERT);
|
X509 x509 = (X509)session.getValue(X509_ATTRIBUTE);
|
||||||
if (x509 == null)
|
if (x509 == null)
|
||||||
{
|
{
|
||||||
Certificate[] certificates = session.getLocalCertificates();
|
Certificate[] certificates = session.getLocalCertificates();
|
||||||
if (certificates == null || certificates.length == 0 || !(certificates[0] instanceof X509Certificate))
|
if (certificates == null || certificates.length == 0 || !(certificates[0] instanceof X509Certificate))
|
||||||
return null;
|
return null;
|
||||||
x509 = new X509(null, (X509Certificate)certificates[0]);
|
x509 = new X509(null, (X509Certificate)certificates[0]);
|
||||||
session.putValue(X509_CERT, x509);
|
session.putValue(X509_ATTRIBUTE, x509);
|
||||||
}
|
}
|
||||||
return x509;
|
return x509;
|
||||||
}
|
}
|
||||||
|
@ -309,57 +324,11 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
return String.format("%s@%x", this.getClass().getSimpleName(), hashCode());
|
return String.format("%s@%x", this.getClass().getSimpleName(), hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class SecureRequest extends Request.Wrapper
|
protected static class SecureRequest extends Request.Wrapper
|
||||||
{
|
{
|
||||||
private final HttpURI _uri;
|
public SecureRequest(Request wrapped)
|
||||||
private final SSLSession _sslSession;
|
|
||||||
private final SslSessionData _sslSessionData;
|
|
||||||
|
|
||||||
private SecureRequest(Request request, HttpURI uri, SSLEngine sslEngine)
|
|
||||||
{
|
{
|
||||||
super(request);
|
super(wrapped);
|
||||||
_uri = uri.asImmutable();
|
|
||||||
|
|
||||||
if (sslEngine == null)
|
|
||||||
{
|
|
||||||
_sslSession = null;
|
|
||||||
_sslSessionData = null;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_sslSession = sslEngine.getSession();
|
|
||||||
checkSni(request, _sslSession);
|
|
||||||
|
|
||||||
String key = SslSessionData.class.getName();
|
|
||||||
SslSessionData sslSessionData = (SslSessionData)_sslSession.getValue(key);
|
|
||||||
if (sslSessionData == null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String cipherSuite = _sslSession.getCipherSuite();
|
|
||||||
int keySize = SslContextFactory.deduceKeyLength(cipherSuite);
|
|
||||||
|
|
||||||
X509Certificate[] certs = getCertChain(getConnectionMetaData().getConnector(), _sslSession);
|
|
||||||
|
|
||||||
byte[] bytes = _sslSession.getId();
|
|
||||||
String idStr = StringUtil.toHexString(bytes);
|
|
||||||
|
|
||||||
sslSessionData = new SslSessionData(keySize, certs, idStr);
|
|
||||||
_sslSession.putValue(key, sslSessionData);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
LOG.warn("Unable to get secure details ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_sslSessionData = sslSessionData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpURI getHttpURI()
|
|
||||||
{
|
|
||||||
return _uri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -367,6 +336,43 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class SecureRequestWithTLSData extends SecureRequest
|
||||||
|
{
|
||||||
|
private final SSLSession _sslSession;
|
||||||
|
private final SslSessionData _sslSessionData;
|
||||||
|
|
||||||
|
public SecureRequestWithTLSData(Request request, SSLEngine sslEngine)
|
||||||
|
{
|
||||||
|
super(request);
|
||||||
|
_sslSession = sslEngine.getSession();
|
||||||
|
checkSni(request, _sslSession);
|
||||||
|
|
||||||
|
String key = SslSessionData.class.getName();
|
||||||
|
SslSessionData sslSessionData = (SslSessionData)_sslSession.getValue(key);
|
||||||
|
if (sslSessionData == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String cipherSuite = _sslSession.getCipherSuite();
|
||||||
|
int keySize = SslContextFactory.deduceKeyLength(cipherSuite);
|
||||||
|
|
||||||
|
X509Certificate[] certs = getCertChain(getConnectionMetaData().getConnector(), _sslSession);
|
||||||
|
|
||||||
|
byte[] bytes = _sslSession.getId();
|
||||||
|
String idStr = StringUtil.toHexString(bytes);
|
||||||
|
|
||||||
|
sslSessionData = new SslSessionData(idStr, keySize, certs);
|
||||||
|
_sslSession.putValue(key, sslSessionData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Unable to get secure details ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_sslSessionData = sslSessionData;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getAttribute(String name)
|
public Object getAttribute(String name)
|
||||||
|
@ -376,14 +382,14 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
{
|
{
|
||||||
if (name.equals(sessionAttribute))
|
if (name.equals(sessionAttribute))
|
||||||
return _sslSession;
|
return _sslSession;
|
||||||
if (name.equals(_sslSessionDataAttribute))
|
if (name.equals(getSslSessionDataAttribute()))
|
||||||
return _sslSessionData;
|
return _sslSessionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return switch (name)
|
return switch (name)
|
||||||
{
|
{
|
||||||
case X509_CERT -> getX509(_sslSession);
|
case X509_ATTRIBUTE -> getX509(_sslSession);
|
||||||
case CERTIFICATES -> _sslSessionData._certs;
|
case PEER_CERTIFICATES_ATTRIBUTE -> _sslSessionData != null ? _sslSessionData.peerCertificates() : null;
|
||||||
default -> super.getAttribute(name);
|
default -> super.getAttribute(name);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -405,32 +411,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
/**
|
/**
|
||||||
* Simple bundle of data that is cached in the SSLSession.
|
* Simple bundle of data that is cached in the SSLSession.
|
||||||
*/
|
*/
|
||||||
public static class SslSessionData
|
public record SslSessionData(String sessionId, int keySize, X509Certificate[] peerCertificates)
|
||||||
{
|
{
|
||||||
private final Integer _keySize;
|
|
||||||
private final X509Certificate[] _certs;
|
|
||||||
private final String _idStr;
|
|
||||||
|
|
||||||
private SslSessionData(Integer keySize, X509Certificate[] certs, String idStr)
|
|
||||||
{
|
|
||||||
this._keySize = keySize;
|
|
||||||
this._certs = certs;
|
|
||||||
this._idStr = idStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getKeySize()
|
|
||||||
{
|
|
||||||
return _keySize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate[] getX509Certificates()
|
|
||||||
{
|
|
||||||
return _certs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId()
|
|
||||||
{
|
|
||||||
return _idStr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server.handler;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -39,6 +39,11 @@ import org.eclipse.jetty.io.ManagedSelector;
|
||||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
import org.eclipse.jetty.io.SelectorManager;
|
import org.eclipse.jetty.io.SelectorManager;
|
||||||
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
import org.eclipse.jetty.io.SocketChannelEndPoint;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpStream;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.Response;
|
||||||
|
import org.eclipse.jetty.server.TunnelSupport;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.HostPort;
|
import org.eclipse.jetty.util.HostPort;
|
||||||
import org.eclipse.jetty.util.IteratingCallback;
|
import org.eclipse.jetty.util.IteratingCallback;
|
|
@ -371,8 +371,8 @@ public class SSLEngineTest
|
||||||
{
|
{
|
||||||
// System.err.println("HANDLE "+request.getRequestURI());
|
// System.err.println("HANDLE "+request.getRequestURI());
|
||||||
SecureRequestCustomizer.SslSessionData sslData = (SecureRequestCustomizer.SslSessionData)
|
SecureRequestCustomizer.SslSessionData sslData = (SecureRequestCustomizer.SslSessionData)
|
||||||
request.getAttribute("org.eclipse.jetty.servlet.request.ssl_session_data");
|
request.getAttribute(SecureRequestCustomizer.DEFAULT_SSL_SESSION_DATA_ATTRIBUTE);
|
||||||
String sslId = sslData.getId();
|
String sslId = sslData.sessionId();
|
||||||
assertNotNull(sslId);
|
assertNotNull(sslId);
|
||||||
|
|
||||||
Fields fields = Request.extractQueryParameters(request);
|
Fields fields = Request.extractQueryParameters(request);
|
||||||
|
|
|
@ -217,15 +217,15 @@ public class ServerConnectorSslServerTest extends HttpServerTestBase
|
||||||
StringBuilder out = new StringBuilder();
|
StringBuilder out = new StringBuilder();
|
||||||
SSLSession session = (SSLSession)request.getAttribute("SSL_SESSION");
|
SSLSession session = (SSLSession)request.getAttribute("SSL_SESSION");
|
||||||
|
|
||||||
SecureRequestCustomizer.SslSessionData data = (SecureRequestCustomizer.SslSessionData)request.getAttribute("SSL_SESSION_data");
|
SecureRequestCustomizer.SslSessionData data = (SecureRequestCustomizer.SslSessionData)request.getAttribute("SSL_SESSIONData");
|
||||||
|
|
||||||
out.append("Hello world").append('\n');
|
out.append("Hello world").append('\n');
|
||||||
out.append("scheme='").append(request.getHttpURI().getScheme()).append("'").append('\n');
|
out.append("scheme='").append(request.getHttpURI().getScheme()).append("'").append('\n');
|
||||||
out.append("isSecure='").append(request.isSecure()).append("'").append('\n');
|
out.append("isSecure='").append(request.isSecure()).append("'").append('\n');
|
||||||
out.append("X509Certificate='").append(data == null ? "" : data.getX509Certificates()).append("'").append('\n');
|
out.append("X509Certificate='").append(data == null ? "" : data.peerCertificates()).append("'").append('\n');
|
||||||
out.append("cipher_suite='").append(session == null ? "" : session.getCipherSuite()).append("'").append('\n');
|
out.append("cipher_suite='").append(session == null ? "" : session.getCipherSuite()).append("'").append('\n');
|
||||||
out.append("key_size='").append(data == null ? "" : data.getKeySize()).append("'").append('\n');
|
out.append("key_size='").append(data == null ? "" : data.keySize()).append("'").append('\n');
|
||||||
out.append("ssl_session_id='").append(data == null ? "" : data.getId()).append("'").append('\n');
|
out.append("ssl_session_id='").append(data == null ? "" : data.sessionId()).append("'").append('\n');
|
||||||
out.append("ssl_session='").append(session).append("'").append('\n');
|
out.append("ssl_session='").append(session).append("'").append('\n');
|
||||||
Content.Sink.write(response, true, out.toString(), callback);
|
Content.Sink.write(response, true, out.toString(), callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<module>jetty-jmx</module>
|
<module>jetty-jmx</module>
|
||||||
<module>jetty-jndi</module>
|
<module>jetty-jndi</module>
|
||||||
<module>jetty-keystore</module>
|
<module>jetty-keystore</module>
|
||||||
|
<module>jetty-proxy</module>
|
||||||
<module>jetty-quic</module>
|
<module>jetty-quic</module>
|
||||||
<module>jetty-rewrite</module>
|
<module>jetty-rewrite</module>
|
||||||
<module>jetty-server</module>
|
<module>jetty-server</module>
|
||||||
|
|
|
@ -16,9 +16,9 @@ package org.eclipse.jetty.ee10.demos;
|
||||||
import org.eclipse.jetty.ee10.proxy.ProxyServlet;
|
import org.eclipse.jetty.ee10.proxy.ProxyServlet;
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
|
|
||||||
public class ProxyServer
|
public class ProxyServer
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||||
|
|
||||||
<Set name="handler">
|
<Set name="handler">
|
||||||
<New class="org.eclipse.jetty.server.ConnectHandler">
|
<New class="org.eclipse.jetty.server.handler.ConnectHandler">
|
||||||
<Set name="handler">
|
<Set name="handler">
|
||||||
<New class="org.eclipse.jetty.ee10.servlet.ServletHandler">
|
<New class="org.eclipse.jetty.ee10.servlet.ServletHandler">
|
||||||
<Call id="proxyHolder" name="addServletWithMapping">
|
<Call id="proxyHolder" name="addServletWithMapping">
|
||||||
|
|
|
@ -45,7 +45,7 @@ import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.RuntimeIOException;
|
import org.eclipse.jetty.io.RuntimeIOException;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.CountingCallback;
|
import org.eclipse.jetty.util.CountingCallback;
|
||||||
|
|
|
@ -28,7 +28,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IteratingCallback;
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
import org.eclipse.jetty.client.util.InputStreamRequestContent;
|
import org.eclipse.jetty.client.util.InputStreamRequestContent;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,9 +16,9 @@ package org.eclipse.jetty.ee10.proxy;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
|
||||||
public abstract class AbstractConnectHandlerTest
|
public abstract class AbstractConnectHandlerTest
|
||||||
|
|
|
@ -139,7 +139,7 @@ public class ClientAuthProxyTest
|
||||||
@Override
|
@Override
|
||||||
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
|
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
|
||||||
{
|
{
|
||||||
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
|
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.PEER_CERTIFICATES_ATTRIBUTE);
|
||||||
Assertions.assertNotNull(certificates);
|
Assertions.assertNotNull(certificates);
|
||||||
X509Certificate certificate = certificates[0];
|
X509Certificate certificate = certificates[0];
|
||||||
X500Principal principal = certificate.getSubjectX500Principal();
|
X500Principal principal = certificate.getSubjectX500Principal();
|
||||||
|
@ -211,7 +211,7 @@ public class ClientAuthProxyTest
|
||||||
|
|
||||||
private static String retrieveUser(HttpServletRequest request)
|
private static String retrieveUser(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
|
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.PEER_CERTIFICATES_ATTRIBUTE);
|
||||||
String clientName = certificates[0].getSubjectX500Principal().getName();
|
String clientName = certificates[0].getSubjectX500Principal().getName();
|
||||||
Matcher matcher = Pattern.compile("CN=([^,]+)").matcher(clientName);
|
Matcher matcher = Pattern.compile("CN=([^,]+)").matcher(clientName);
|
||||||
if (matcher.find())
|
if (matcher.find())
|
||||||
|
|
|
@ -32,12 +32,12 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.Net;
|
import org.eclipse.jetty.toolchain.test.Net;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||||
|
@ -42,6 +41,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.toolchain.test.Net;
|
import org.eclipse.jetty.toolchain.test.Net;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
|
@ -48,13 +48,13 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.FormFields;
|
import org.eclipse.jetty.server.FormFields;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.toolchain.test.Net;
|
import org.eclipse.jetty.toolchain.test.Net;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
|
@ -15,9 +15,9 @@ package org.eclipse.jetty.ee10.proxy;
|
||||||
|
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
import org.eclipse.jetty.ee10.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
|
|
||||||
public class ProxyServer
|
public class ProxyServer
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.ee10.proxy.LEVEL=DEBUG
|
#org.eclipse.jetty.ee10.proxy.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||||
#org.eclipse.jetty.server.ConnectHandler.LEVEL=DEBUG
|
#org.eclipse.jetty.server.handler.ConnectHandler.LEVEL=DEBUG
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.ee10.servlet.security.UserAuthentication;
|
||||||
import org.eclipse.jetty.ee10.servlet.security.UserIdentity;
|
import org.eclipse.jetty.ee10.servlet.security.UserIdentity;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.SecureRequestCustomizer.SslSessionData;
|
import org.eclipse.jetty.server.SecureRequestCustomizer.SslSessionData;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.security.Constraint;
|
import org.eclipse.jetty.util.security.Constraint;
|
||||||
|
@ -63,14 +64,14 @@ public class SslClientCertAuthenticator extends LoginAuthenticator
|
||||||
|
|
||||||
//TODO this seems fragile, to rely on this name
|
//TODO this seems fragile, to rely on this name
|
||||||
//X509Certificate[] certs = (X509Certificate[])req.getAttribute("jakarta.servlet.request.X509Certificate");
|
//X509Certificate[] certs = (X509Certificate[])req.getAttribute("jakarta.servlet.request.X509Certificate");
|
||||||
SslSessionData sslSessionData = (SslSessionData)req.getAttribute("org.eclipse.jetty.servlet.request.ssl_session_data");
|
SslSessionData sslSessionData = (SslSessionData)req.getAttribute(SecureRequestCustomizer.DEFAULT_SSL_SESSION_DATA_ATTRIBUTE);
|
||||||
if (sslSessionData == null)
|
if (sslSessionData == null)
|
||||||
{
|
{
|
||||||
Response.writeError(req, res, callback, HttpServletResponse.SC_FORBIDDEN);
|
Response.writeError(req, res, callback, HttpServletResponse.SC_FORBIDDEN);
|
||||||
return Authentication.SEND_FAILURE;
|
return Authentication.SEND_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
X509Certificate[] certs = sslSessionData.getX509Certificates();
|
X509Certificate[] certs = sslSessionData.peerCertificates();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,9 +16,9 @@ package org.eclipse.jetty.ee9.demos;
|
||||||
import org.eclipse.jetty.ee9.proxy.ProxyServlet;
|
import org.eclipse.jetty.ee9.proxy.ProxyServlet;
|
||||||
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
|
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
|
|
||||||
public class ProxyServer
|
public class ProxyServer
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,7 +45,7 @@ import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.RuntimeIOException;
|
import org.eclipse.jetty.io.RuntimeIOException;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.CountingCallback;
|
import org.eclipse.jetty.util.CountingCallback;
|
||||||
|
|
|
@ -28,7 +28,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IteratingCallback;
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||||
import org.eclipse.jetty.client.util.InputStreamRequestContent;
|
import org.eclipse.jetty.client.util.InputStreamRequestContent;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,9 +16,9 @@ package org.eclipse.jetty.ee9.proxy;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
|
||||||
public abstract class AbstractConnectHandlerTest
|
public abstract class AbstractConnectHandlerTest
|
||||||
|
|
|
@ -139,7 +139,7 @@ public class ClientAuthProxyTest
|
||||||
@Override
|
@Override
|
||||||
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
|
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
|
||||||
{
|
{
|
||||||
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
|
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.PEER_CERTIFICATES_ATTRIBUTE);
|
||||||
Assertions.assertNotNull(certificates);
|
Assertions.assertNotNull(certificates);
|
||||||
X509Certificate certificate = certificates[0];
|
X509Certificate certificate = certificates[0];
|
||||||
X500Principal principal = certificate.getSubjectX500Principal();
|
X500Principal principal = certificate.getSubjectX500Principal();
|
||||||
|
@ -211,7 +211,7 @@ public class ClientAuthProxyTest
|
||||||
|
|
||||||
private static String retrieveUser(HttpServletRequest request)
|
private static String retrieveUser(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
|
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.PEER_CERTIFICATES_ATTRIBUTE);
|
||||||
String clientName = certificates[0].getSubjectX500Principal().getName();
|
String clientName = certificates[0].getSubjectX500Principal().getName();
|
||||||
Matcher matcher = Pattern.compile("CN=([^,]+)").matcher(clientName);
|
Matcher matcher = Pattern.compile("CN=([^,]+)").matcher(clientName);
|
||||||
if (matcher.find())
|
if (matcher.find())
|
||||||
|
|
|
@ -32,12 +32,12 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.Net;
|
import org.eclipse.jetty.toolchain.test.Net;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.Promise;
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
|
|
@ -33,7 +33,6 @@ import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||||
|
@ -42,6 +41,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.toolchain.test.Net;
|
import org.eclipse.jetty.toolchain.test.Net;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
|
|
@ -48,13 +48,13 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.io.ClientConnector;
|
import org.eclipse.jetty.io.ClientConnector;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.FormFields;
|
import org.eclipse.jetty.server.FormFields;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.toolchain.test.Net;
|
import org.eclipse.jetty.toolchain.test.Net;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
|
@ -15,9 +15,9 @@ package org.eclipse.jetty.ee9.proxy;
|
||||||
|
|
||||||
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
|
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
import org.eclipse.jetty.ee9.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.server.ConnectHandler;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||||
|
|
||||||
public class ProxyServer
|
public class ProxyServer
|
||||||
{
|
{
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class SslClientCertAuthenticator extends LoginAuthenticator
|
||||||
|
|
||||||
HttpServletRequest request = (HttpServletRequest)req;
|
HttpServletRequest request = (HttpServletRequest)req;
|
||||||
HttpServletResponse response = (HttpServletResponse)res;
|
HttpServletResponse response = (HttpServletResponse)res;
|
||||||
X509Certificate[] certs = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
|
X509Certificate[] certs = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.PEER_CERTIFICATES_ATTRIBUTE);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue