Re-enabled jetty-ee9-proxy and jetty-ee10-proxy modules. (#8289)

* Re-enabled jetty-ee9-proxy and jetty-ee10-proxy modules.
Introduced TunnelSupport to abstract out the tunnelling capabilities.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2022-07-22 13:36:47 +02:00 committed by GitHub
parent dde0b4f6c1
commit 8cb09e4c59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 973 additions and 1595 deletions

View File

@ -478,9 +478,16 @@ public abstract class HttpSender
Content.Source content = request.getBody();
if (expect100)
chunk = Content.Chunk.EMPTY;
{
if (committed)
return Action.IDLE;
else
chunk = null;
}
else
{
chunk = content.read();
}
if (LOG.isDebugEnabled())
LOG.debug("Content {} for {}", chunk, request);

View File

@ -17,7 +17,6 @@ import java.nio.ByteBuffer;
import java.util.stream.Stream;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.content.AsyncContent;
import org.eclipse.jetty.util.Callback;
@ -44,6 +43,6 @@ public class AsyncRequestContent extends AsyncContent implements Request.Content
public void write(ByteBuffer buffer, Callback callback)
{
write(Content.Chunk.from(buffer, false), callback);
write(false, buffer, callback);
}
}

View File

@ -50,12 +50,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
@Override
public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
{
if (byteBuffer != null)
write(Content.Chunk.from(byteBuffer, last), callback);
else if (last)
write(Content.Chunk.EOF, callback);
else
write(Content.Chunk.EMPTY, callback);
write(Content.Chunk.from(byteBuffer, last, callback::succeeded), callback);
}
public void write(Content.Chunk chunk, Callback callback)
@ -171,7 +166,6 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
if (chunks.isEmpty())
l.signal();
}
current.callback().succeeded();
return current.chunk();
}
@ -228,11 +222,7 @@ public class AsyncContent implements Content.Sink, Content.Source, Closeable
drained = List.copyOf(chunks);
chunks.clear();
}
drained.forEach(cc ->
{
cc.chunk().release();
cc.callback().failed(failure);
});
drained.forEach(cc -> cc.callback().failed(failure));
invoker.run(this::invokeDemandCallback);
}

View File

@ -43,8 +43,8 @@ public class ContentSourceInputStream extends InputStream
@Override
public int read() throws IOException
{
read(oneByte, 0, 1);
return oneByte[0] & 0xFF;
int read = read(oneByte, 0, 1);
return read < 0 ? -1 : oneByte[0] & 0xFF;
}
@Override

View File

@ -11,7 +11,7 @@
// ========================================================================
//
package org.eclipse.jetty.ee10.proxy;
package org.eclipse.jetty.server;
import java.io.Closeable;
import java.io.IOException;
@ -26,14 +26,12 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
@ -41,11 +39,9 @@ import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
@ -57,7 +53,7 @@ import org.slf4j.LoggerFactory;
*/
public class ConnectHandler extends Handler.Wrapper
{
protected static final Logger LOG = LoggerFactory.getLogger(ConnectHandler.class);
private static final Logger LOG = LoggerFactory.getLogger(ConnectHandler.class);
private final Set<String> whiteList = new HashSet<>();
private final Set<String> blackList = new HashSet<>();
@ -187,28 +183,25 @@ public class ConnectHandler extends Handler.Wrapper
@Override
public Request.Processor handle(Request request) throws Exception
{
String tunnelProtocol = request.getConnectionMetaData().getProtocol();
if (HttpMethod.CONNECT.is(request.getMethod()) && tunnelProtocol == null)
if (HttpMethod.CONNECT.is(request.getMethod()))
{
//we will fully handle this request
return (req, res, callback) ->
TunnelSupport tunnelSupport = request.getTunnelSupport();
if (tunnelSupport != null)
{
String serverAddress = req.getHttpURI().getHost() + ":" + req.getHttpURI().getPort();
if (HttpVersion.HTTP_2.is(request.getConnectionMetaData().getProtocol()))
if (tunnelSupport.getProtocol() == null)
{
HttpURI httpURI = request.getHttpURI();
serverAddress = httpURI.getHost() + ":" + httpURI.getPort();
return (req, res, cbk) ->
{
HttpURI httpURI = req.getHttpURI();
String serverAddress = httpURI.getAuthority();
if (LOG.isDebugEnabled())
LOG.debug("CONNECT request for {}", serverAddress);
handleConnect(req, res, cbk, serverAddress);
};
}
if (LOG.isDebugEnabled())
LOG.debug("CONNECT request for {}", serverAddress);
handleConnect(req, res, callback, serverAddress);
};
}
}
else
{
//we're not handling this request
return super.handle(request);
}
return super.handle(request);
}
/**
@ -223,15 +216,14 @@ public class ConnectHandler extends Handler.Wrapper
*/
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
{
//TODO fix me
/* try
try
{
boolean proceed = handleAuthentication(request, response, serverAddress);
if (!proceed)
{
if (LOG.isDebugEnabled())
LOG.debug("Missing proxy authentication");
sendConnectResponse(request, response, callback, HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
sendConnectResponse(request, response, callback, HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
return;
}
@ -243,22 +235,10 @@ public class ConnectHandler extends Handler.Wrapper
{
if (LOG.isDebugEnabled())
LOG.debug("Destination {}:{} forbidden", host, port);
sendConnectResponse(request, response, callback, HttpServletResponse.SC_FORBIDDEN);
sendConnectResponse(request, response, callback, HttpStatus.FORBIDDEN_403);
return;
}
HttpChannel httpChannel = baseRequest.getHttpChannel();
if (!httpChannel.isTunnellingSupported())
{
if (LOG.isDebugEnabled())
LOG.debug("CONNECT not supported for {}", httpChannel);
sendConnectResponse(request, response, callback, HttpServletResponse.SC_FORBIDDEN);
return;
}
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(0);
if (LOG.isDebugEnabled())
LOG.debug("Connecting to {}:{}", host, port);
@ -267,7 +247,7 @@ public class ConnectHandler extends Handler.Wrapper
@Override
public void succeeded(SocketChannel channel)
{
ConnectContext connectContext = new ConnectContext(request, response, asyncContext, httpChannel.getTunnellingEndPoint());
ConnectContext connectContext = new ConnectContext(request, response, callback, request.getTunnelSupport().getEndPoint());
if (channel.isConnected())
selector.accept(channel, connectContext);
else
@ -277,17 +257,17 @@ public class ConnectHandler extends Handler.Wrapper
@Override
public void failed(Throwable x)
{
onConnectFailure(request, response, asyncContext, x);
onConnectFailure(request, response, callback, x);
}
});
}
catch (Exception x)
{
onConnectFailure(request, response, null, x);
}*/
onConnectFailure(request, response, callback, x);
}
}
protected void connectToServer(HttpServletRequest request, String host, int port, Promise<SocketChannel> promise)
protected void connectToServer(Request request, String host, int port, Promise<SocketChannel> promise)
{
SocketChannel channel = null;
try
@ -334,7 +314,7 @@ public class ConnectHandler extends Handler.Wrapper
protected void onConnectSuccess(ConnectContext connectContext, UpstreamConnection upstreamConnection)
{
ConcurrentMap<String, Object> context = connectContext.getContext();
HttpServletRequest request = connectContext.getRequest();
Request request = connectContext.getRequest();
prepareContext(request, context);
EndPoint downstreamEndPoint = connectContext.getEndPoint();
@ -346,23 +326,16 @@ public class ConnectHandler extends Handler.Wrapper
if (LOG.isDebugEnabled())
LOG.debug("Connection setup completed: {}<->{}", downstreamConnection, upstreamConnection);
HttpServletResponse response = connectContext.getResponse();
//TODO fix me
//sendConnectResponse(request, response, HttpServletResponse.SC_OK);
upgradeConnection(request, response, downstreamConnection);
connectContext.getAsyncContext().complete();
Response response = connectContext.getResponse();
upgradeConnection(request, downstreamConnection);
sendConnectResponse(request, response, connectContext.callback, HttpStatus.OK_200);
}
protected void onConnectFailure(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, Throwable failure)
protected void onConnectFailure(Request request, Response response, Callback callback, Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("CONNECT failed", failure);
//TODO fix me
//sendConnectResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
if (asyncContext != null)
asyncContext.complete();
sendConnectResponse(request, response, callback, HttpStatus.INTERNAL_SERVER_ERROR_500);
}
private void sendConnectResponse(Request request, Response response, Callback callback, int statusCode)
@ -370,14 +343,14 @@ public class ConnectHandler extends Handler.Wrapper
try
{
response.getHeaders().putLongField(HttpHeader.CONTENT_LENGTH, 0);
if (statusCode != HttpServletResponse.SC_OK)
if (statusCode != HttpStatus.OK_200)
{
response.getHeaders().put(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
response.getHeaders().put(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
Response.writeError(request, response, callback, statusCode);
}
else
{
response.setStatus(HttpServletResponse.SC_OK);
response.setStatus(HttpStatus.OK_200);
callback.succeeded();
}
if (LOG.isDebugEnabled())
@ -414,15 +387,15 @@ public class ConnectHandler extends Handler.Wrapper
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext);
}
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
protected void prepareContext(Request request, ConcurrentMap<String, Object> context)
{
}
private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection)
private void upgradeConnection(Request request, Connection connection)
{
// Set the new connection as request attribute so that
// Jetty understands that it has to upgrade the connection.
request.setAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE, connection);
request.setAttribute(HttpStream.UPGRADE_CONNECTION_ATTRIBUTE, connection);
if (LOG.isDebugEnabled())
LOG.debug("Upgraded connection to {}", connection);
}
@ -439,10 +412,7 @@ public class ConnectHandler extends Handler.Wrapper
*/
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException
{
int read = endPoint.fill(buffer);
if (LOG.isDebugEnabled())
LOG.debug("{} read {} bytes", this, read);
return read;
return endPoint.fill(buffer);
}
/**
@ -455,8 +425,6 @@ public class ConnectHandler extends Handler.Wrapper
*/
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback, ConcurrentMap<String, Object> context)
{
if (LOG.isDebugEnabled())
LOG.debug("{} writing {} bytes", this, buffer.remaining());
endPoint.write(callback, buffer);
}
@ -532,23 +500,23 @@ public class ConnectHandler extends Handler.Wrapper
{
close(channel);
ConnectContext connectContext = (ConnectContext)attachment;
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
onConnectFailure(connectContext.request, connectContext.response, connectContext.callback, ex);
}
}
protected static class ConnectContext
{
private final ConcurrentMap<String, Object> context = new ConcurrentHashMap<>();
private final HttpServletRequest request;
private final HttpServletResponse response;
private final AsyncContext asyncContext;
private final Request request;
private final Response response;
private final Callback callback;
private final EndPoint endPoint;
public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, EndPoint endPoint)
public ConnectContext(Request request, Response response, Callback callback, EndPoint endPoint)
{
this.request = request;
this.response = response;
this.asyncContext = asyncContext;
this.callback = callback;
this.endPoint = endPoint;
}
@ -557,19 +525,19 @@ public class ConnectHandler extends Handler.Wrapper
return context;
}
public HttpServletRequest getRequest()
public Request getRequest()
{
return request;
}
public HttpServletResponse getResponse()
public Response getResponse()
{
return response;
}
public AsyncContext getAsyncContext()
public Callback getCallback()
{
return asyncContext;
return callback;
}
public EndPoint getEndPoint()
@ -578,9 +546,9 @@ public class ConnectHandler extends Handler.Wrapper
}
}
public class UpstreamConnection extends ProxyConnection
public class UpstreamConnection extends TunnelConnection
{
private ConnectContext connectContext;
private final ConnectContext connectContext;
public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectContext connectContext)
{
@ -592,24 +560,29 @@ public class ConnectHandler extends Handler.Wrapper
public void onOpen()
{
super.onOpen();
onConnectSuccess(connectContext, UpstreamConnection.this);
onConnectSuccess(connectContext, this);
fillInterested();
}
@Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
{
return ConnectHandler.this.read(endPoint, buffer, getContext());
int read = ConnectHandler.this.read(endPoint, buffer, getContext());
if (LOG.isDebugEnabled())
LOG.debug("Read {} bytes from server {}", read, this);
return read;
}
@Override
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("Writing {} bytes to client {}", buffer.remaining(), this);
ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}
}
public class DownstreamConnection extends ProxyConnection implements Connection.UpgradeTo
public class DownstreamConnection extends TunnelConnection implements Connection.UpgradeTo
{
private ByteBuffer buffer;
@ -643,7 +616,7 @@ public class ConnectHandler extends Handler.Wrapper
{
buffer = null;
if (LOG.isDebugEnabled())
LOG.debug("{} wrote initial {} bytes to server", DownstreamConnection.this, remaining);
LOG.debug("Wrote initial {} bytes to server {}", remaining, DownstreamConnection.this);
fillInterested();
}
@ -652,7 +625,7 @@ public class ConnectHandler extends Handler.Wrapper
{
buffer = null;
if (LOG.isDebugEnabled())
LOG.debug("{} failed to write initial {} bytes to server", this, remaining, x);
LOG.debug("Failed to write initial {} bytes to server {}", remaining, DownstreamConnection.this, x);
close();
getConnection().close();
}
@ -662,13 +635,149 @@ public class ConnectHandler extends Handler.Wrapper
@Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
{
return ConnectHandler.this.read(endPoint, buffer, getContext());
int read = ConnectHandler.this.read(endPoint, buffer, getContext());
if (LOG.isDebugEnabled())
LOG.debug("Read {} bytes from client {}", read, this);
return read;
}
@Override
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("Writing {} bytes to server {}", buffer.remaining(), this);
ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}
}
private abstract static class TunnelConnection extends AbstractConnection
{
private final IteratingCallback pipe = new ProxyIteratingCallback();
private final ByteBufferPool bufferPool;
private final ConcurrentMap<String, Object> context;
private TunnelConnection connection;
protected TunnelConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
{
super(endPoint, executor);
this.bufferPool = bufferPool;
this.context = context;
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public ConcurrentMap<String, Object> getContext()
{
return context;
}
public Connection getConnection()
{
return connection;
}
public void setConnection(TunnelConnection connection)
{
this.connection = connection;
}
@Override
public void onFillable()
{
pipe.iterate();
}
protected abstract int read(EndPoint endPoint, ByteBuffer buffer) throws IOException;
protected abstract void write(EndPoint endPoint, ByteBuffer buffer, Callback callback);
protected void close(Throwable failure)
{
getEndPoint().close(failure);
}
@Override
public String toConnectionString()
{
EndPoint endPoint = getEndPoint();
return String.format("%s@%x[l:%s<=>r:%s]",
getClass().getSimpleName(),
hashCode(),
endPoint.getLocalSocketAddress(),
endPoint.getRemoteSocketAddress());
}
private class ProxyIteratingCallback extends IteratingCallback
{
private ByteBuffer buffer;
private int filled;
@Override
protected Action process()
{
buffer = bufferPool.acquire(getInputBufferSize(), true);
try
{
int filled = this.filled = read(getEndPoint(), buffer);
if (filled > 0)
{
write(connection.getEndPoint(), buffer, this);
return Action.SCHEDULED;
}
else if (filled == 0)
{
bufferPool.release(buffer);
fillInterested();
return Action.IDLE;
}
else
{
bufferPool.release(buffer);
connection.getEndPoint().shutdownOutput();
return Action.SUCCEEDED;
}
}
catch (IOException x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not fill {}", TunnelConnection.this, x);
bufferPool.release(buffer);
disconnect(x);
return Action.SUCCEEDED;
}
}
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("Wrote {} bytes {}", filled, TunnelConnection.this);
bufferPool.release(buffer);
super.succeeded();
}
@Override
protected void onCompleteSuccess()
{
}
@Override
protected void onCompleteFailure(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Failed to write {} bytes {}", filled, TunnelConnection.this, x);
bufferPool.release(buffer);
disconnect(x);
}
private void disconnect(Throwable x)
{
TunnelConnection.this.close(x);
connection.close(x);
}
}
}
}

View File

@ -23,12 +23,6 @@ import org.eclipse.jetty.util.HostPort;
public interface ConnectionMetaData extends Attributes
{
/**
* Attribute used to get the {@link Connection} from the request attributes. This should not be used to set the
* connection as a request attribute, instead use {@link HttpStream#setUpgradeConnection(Connection)}.
*/
String UPGRADE_CONNECTION_ATTRIBUTE = ConnectionMetaData.class.getName() + ".UPGRADE";
/**
* @return a unique (within the lifetime of the JVM) identifier string for the network connection to the JVM
*/

View File

@ -25,6 +25,12 @@ import org.eclipse.jetty.util.Callback;
public interface HttpStream extends Callback
{
/**
* Attribute used to get the {@link Connection} from the request attributes. This should not be used to set the
* connection as a request attribute, instead use {@link HttpStream#setUpgradeConnection(Connection)}.
*/
String UPGRADE_CONNECTION_ATTRIBUTE = HttpStream.class.getName() + ".UPGRADE";
/**
* @return an ID unique within the lifetime scope of the associated protocol connection.
* This may be a protocol ID (eg HTTP/2 stream ID) or it may be unrelated to the protocol.
@ -67,6 +73,11 @@ public interface HttpStream extends Callback
Connection upgrade();
default TunnelSupport getTunnelSupport()
{
return null;
}
default Throwable consumeAvailable()
{
while (true)
@ -176,6 +187,12 @@ public interface HttpStream extends Callback
return getWrapped().upgrade();
}
@Override
public TunnelSupport getTunnelSupport()
{
return getWrapped().getTunnelSupport();
}
@Override
public final Throwable consumeAvailable()
{

View File

@ -196,6 +196,8 @@ public interface Request extends Attributes, Content.Source
*/
boolean addErrorListener(Predicate<Throwable> onError);
TunnelSupport getTunnelSupport();
void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper);
static String getLocalAddr(Request request)
@ -550,6 +552,12 @@ public interface Request extends Attributes, Content.Source
getWrapped().fail(failure);
}
@Override
public boolean isPushSupported()
{
return getWrapped().isPushSupported();
}
@Override
public void push(MetaData.Request request)
{
@ -562,6 +570,12 @@ public interface Request extends Attributes, Content.Source
return getWrapped().addErrorListener(onError);
}
@Override
public TunnelSupport getTunnelSupport()
{
return getWrapped().getTunnelSupport();
}
@Override
public void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper)
{

View File

@ -0,0 +1,38 @@
//
// ========================================================================
// 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.server;
import org.eclipse.jetty.io.EndPoint;
/**
* <p>Supports the implementation of HTTP {@code CONNECT} tunnels.</p>
*/
public interface TunnelSupport
{
/**
* <p>Returns the protocol of the {@code CONNECT} tunnel,
* or {@code null} if the tunnel transports HTTP or opaque bytes.</p>
*
* @return the {@code CONNECT} tunnel protocol, or {@code null} for HTTP
*/
String getProtocol();
/**
* <p>Returns the {@link EndPoint} that should be used to carry the
* tunneled protocol.</p>
*
* @return the {@code CONNECT} tunnel {@link EndPoint}
*/
EndPoint getEndPoint();
}

View File

@ -54,6 +54,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.TunnelSupport;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@ -996,6 +997,12 @@ public class HttpChannelState implements HttpChannel, Components
// TODO
}
@Override
public boolean isPushSupported()
{
return true;
}
@Override
public void push(MetaData.Request request)
{
@ -1030,6 +1037,12 @@ public class HttpChannelState implements HttpChannel, Components
}
}
@Override
public TunnelSupport getTunnelSupport()
{
return getStream().getTunnelSupport();
}
@Override
public void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper)
{

View File

@ -62,6 +62,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.TunnelSupport;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
@ -82,6 +83,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
private static final ThreadLocal<HttpConnection> __currentConnection = new ThreadLocal<>();
private static final AtomicLong __connectionIdGenerator = new AtomicLong();
private final TunnelSupport _tunnelSupport = new TunnelSupportOverHTTP1();
private final AtomicLong _streamIdGenerator = new AtomicLong();
private final long _id;
private final HttpConfiguration _configuration;
@ -939,7 +941,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
protected void onCompleteSuccess()
{
// TODO is this too late to get the request? And is that the right attribute and the right thing to do?
boolean upgrading = _httpChannel.getRequest() != null && _httpChannel.getRequest().getAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE) != null;
boolean upgrading = _httpChannel.getRequest() != null && _httpChannel.getRequest().getAttribute(HttpStream.UPGRADE_CONNECTION_ATTRIBUTE) != null;
release().succeeded();
// If successfully upgraded it is responsibility of the next protocol to close the connection.
if (_shutdownOut && !upgrading)
@ -1484,7 +1486,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
{
_upgradeConnection = connection;
if (_httpChannel.getRequest() != null)
_httpChannel.getRequest().setAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE, connection);
_httpChannel.getRequest().setAttribute(HttpStream.UPGRADE_CONNECTION_ATTRIBUTE, connection);
}
@Override
@ -1541,6 +1543,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
return upgradeConnection;
}
@Override
public TunnelSupport getTunnelSupport()
{
return _tunnelSupport;
}
@Override
public void succeeded()
{
@ -1558,6 +1566,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
return;
}
// Save the upgrade Connection before recycling the HttpChannel which would clear the request attributes.
_upgradeConnection = (Connection)_httpChannel.getRequest().getAttribute(HttpStream.UPGRADE_CONNECTION_ATTRIBUTE);
_httpChannel.recycle();
if (HttpConnection.this.upgrade(stream))
@ -1648,4 +1659,19 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
return HttpStream.super.getInvocationType();
}
}
private class TunnelSupportOverHTTP1 implements TunnelSupport
{
@Override
public String getProtocol()
{
return null;
}
@Override
public EndPoint getEndPoint()
{
return HttpConnection.this.getEndPoint();
}
}
}

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.TunnelSupport;
public class TestableRequest implements Request
{
@ -152,11 +153,23 @@ public class TestableRequest implements Request
return false;
}
@Override
public boolean isPushSupported()
{
return false;
}
@Override
public void push(org.eclipse.jetty.http.MetaData.Request request)
{
}
@Override
public TunnelSupport getTunnelSupport()
{
return null;
}
@Override
public void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper)
{

View File

@ -137,7 +137,7 @@ public abstract class AbstractHandshaker implements Handshaker
// We need to also manually set upgrade attribute because stream wrapper succeeded is run after
// the decision is made to close the connection.
request.setAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE, connection);
request.setAttribute(HttpStream.UPGRADE_CONNECTION_ATTRIBUTE, connection);
request.addHttpStreamWrapper(s -> new HttpStream.Wrapper(s)
{
@Override

View File

@ -13,10 +13,10 @@
package org.eclipse.jetty.ee10.demos;
import org.eclipse.jetty.ee10.proxy.ConnectHandler;
import org.eclipse.jetty.ee10.proxy.ProxyServlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;

View File

@ -25,7 +25,6 @@
<argLine>
@{argLine} ${jetty.surefire.argLine} --add-reads org.eclipse.jetty.ee10.proxy=org.eclipse.jetty.logging
</argLine>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>

View File

@ -4,7 +4,7 @@
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Set name="handler">
<New class="org.eclipse.jetty.ee10.proxy.ConnectHandler">
<New class="org.eclipse.jetty.server.ConnectHandler">
<Set name="handler">
<New class="org.eclipse.jetty.ee10.servlet.ServletHandler">
<Call id="proxyHolder" name="addServletWithMapping">

View File

@ -45,6 +45,7 @@ import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CountingCallback;

View File

@ -28,6 +28,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
@ -98,7 +99,7 @@ public class AsyncProxyServlet extends ProxyServlet
/**
* <p>Convenience extension of {@link AsyncProxyServlet} that offers transparent proxy functionalities.</p>
*
* @see org.eclipse.jetty.proxy.AbstractProxyServlet.TransparentDelegate
* @see AbstractProxyServlet.TransparentDelegate
*/
public static class Transparent extends AsyncProxyServlet
{

View File

@ -1,161 +0,0 @@
//
// ========================================================================
// 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.ee10.proxy;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
import org.slf4j.Logger;
public abstract class ProxyConnection extends AbstractConnection
{
protected static final Logger LOG = ConnectHandler.LOG;
private final IteratingCallback pipe = new ProxyIteratingCallback();
private final ByteBufferPool bufferPool;
private final ConcurrentMap<String, Object> context;
private ProxyConnection connection;
protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
{
super(endp, executor);
this.bufferPool = bufferPool;
this.context = context;
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public ConcurrentMap<String, Object> getContext()
{
return context;
}
public Connection getConnection()
{
return connection;
}
public void setConnection(ProxyConnection connection)
{
this.connection = connection;
}
@Override
public void onFillable()
{
pipe.iterate();
}
protected abstract int read(EndPoint endPoint, ByteBuffer buffer) throws IOException;
protected abstract void write(EndPoint endPoint, ByteBuffer buffer, Callback callback);
protected void close(Throwable failure)
{
getEndPoint().close(failure);
}
@Override
public String toConnectionString()
{
EndPoint endPoint = getEndPoint();
return String.format("%s@%x[l:%s<=>r:%s]",
getClass().getSimpleName(),
hashCode(),
endPoint.getLocalSocketAddress(),
endPoint.getRemoteSocketAddress());
}
private class ProxyIteratingCallback extends IteratingCallback
{
private ByteBuffer buffer;
private int filled;
@Override
protected Action process()
{
buffer = bufferPool.acquire(getInputBufferSize(), true);
try
{
int filled = this.filled = read(getEndPoint(), buffer);
if (LOG.isDebugEnabled())
LOG.debug("{} filled {} bytes", ProxyConnection.this, filled);
if (filled > 0)
{
write(connection.getEndPoint(), buffer, this);
return Action.SCHEDULED;
}
else if (filled == 0)
{
bufferPool.release(buffer);
fillInterested();
return Action.IDLE;
}
else
{
bufferPool.release(buffer);
connection.getEndPoint().shutdownOutput();
return Action.SUCCEEDED;
}
}
catch (IOException x)
{
if (LOG.isDebugEnabled())
LOG.debug("{} could not fill", ProxyConnection.this, x);
bufferPool.release(buffer);
disconnect(x);
return Action.SUCCEEDED;
}
}
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("{} wrote {} bytes", ProxyConnection.this, filled);
bufferPool.release(buffer);
super.succeeded();
}
@Override
protected void onCompleteSuccess()
{
}
@Override
protected void onCompleteFailure(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("{} failed to write {} bytes", ProxyConnection.this, filled, x);
bufferPool.release(buffer);
disconnect(x);
}
private void disconnect(Throwable x)
{
ProxyConnection.this.close(x);
connection.close(x);
}
}
}

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.util.Callback;
/**

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.ee10.proxy;
import java.io.IOException;
import java.net.Socket;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.jupiter.api.AfterEach;

View File

@ -75,6 +75,7 @@ import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ajax.JSON;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
@ -781,41 +782,37 @@ public class AsyncMiddleManServletTest
@Test
public void testClientRequestReadFailsOnSecondRead() throws Exception
{
//TODO fix me
/* try (StacklessLogging ignored = new StacklessLogging(HttpChannelState.class))
startServer(new EchoHttpServlet());
startProxy(new AsyncMiddleManServlet()
{
startServer(new EchoHttpServlet());
startProxy(new AsyncMiddleManServlet()
private int count;
@Override
protected int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
{
private int count;
@Override
protected int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
{
if (++count < 2)
return super.readClientRequestContent(input, buffer);
else
throw new IOException("explicitly_thrown_by_test");
}
if (++count < 2)
return super.readClientRequestContent(input, buffer);
else
throw new IOException("explicitly_thrown_by_test");
}
});
startClient();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == 502)
latch.countDown();
});
startClient();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == 502)
latch.countDown();
});
content.offer(ByteBuffer.allocate(512));
sleep(1000);
content.offer(ByteBuffer.allocate(512));
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}*/
content.write(ByteBuffer.allocate(512), Callback.NOOP);
sleep(1000);
content.write(ByteBuffer.allocate(512), Callback.NOOP);
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
@ -1109,6 +1106,7 @@ public class AsyncMiddleManServletTest
}
@Test
@Disabled("idle timeouts do not work yet")
public void testAfterContentTransformerClosingFilesOnClientRequestException() throws Exception
{
Path targetTestsDir = workDir.getEmptyPathDir();

View File

@ -173,8 +173,6 @@ public class BalancerServletTest
RewriteHandler rewrite = new RewriteHandler();
rewrite.setHandler(balancer.getHandler());
balancer.setHandler(rewrite);
//TODO can't find method?
//rewrite.setRewriteRequestURI(true);
rewrite.addRule(new VirtualHostRuleContainer());
balancer.start();
@ -182,7 +180,7 @@ public class BalancerServletTest
assertThat(response.getStatus(), is(200));
assertThat(response.getContentAsString(), containsString("requestURI='/context/mapping/test/%0A'"));
assertThat(response.getContentAsString(), containsString("servletPath='/mapping'"));
assertThat(response.getContentAsString(), containsString("pathInfo='/test/\n'"));
assertThat(response.getContentAsString(), containsString("pathInfo='/test/%0A'"));
}
private String readFirstLine(byte[] responseBytes) throws IOException

View File

@ -15,16 +15,20 @@ package org.eclipse.jetty.ee10.proxy;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;
import jakarta.servlet.http.HttpServletRequest;
import org.eclipse.jetty.client.HttpClient;
@ -40,6 +44,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
@ -129,20 +134,17 @@ public class ClientAuthProxyTest
private void startServer() throws Exception
{
startServer(new EmptyServerHandler()
startServer(new Handler.Processor()
{
@Override
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback) throws Exception
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
{
//TODO fix me
/* X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE);
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
Assertions.assertNotNull(certificates);
X509Certificate certificate = certificates[0];
X500Principal principal = certificate.getSubjectX500Principal();
ServletOutputStream output = response.getOutputStream();
output.println(principal.toString());
output.println(request.getRemotePort());*/
String body = "%s\r\n%d\r\n".formatted(principal.toString(), org.eclipse.jetty.server.Request.getRemotePort(request));
Content.Sink.write(response, true, body, callback);
}
});
}
@ -209,15 +211,14 @@ public class ClientAuthProxyTest
private static String retrieveUser(HttpServletRequest request)
{
//TODO fix me
/* X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE);
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
String clientName = certificates[0].getSubjectX500Principal().getName();
Matcher matcher = Pattern.compile("CN=([^,]+)").matcher(clientName);
if (matcher.find())
{
// Retain only "userN".
return matcher.group(1).split("_")[0];
}*/
}
return null;
}

View File

@ -13,15 +13,20 @@
package org.eclipse.jetty.ee10.proxy;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import jakarta.servlet.ServletException;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@ -150,17 +155,17 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
// TODO fix me
/* String uri = httpRequest.getRequestURI();
String uri = request.getPathInContext();
if ("/echo".equals(uri))
{
StringBuilder builder = new StringBuilder();
builder.append(httpRequest.getMethod()).append(" ").append(uri);
if (httpRequest.getQueryString() != null)
builder.append("?").append(httpRequest.getQueryString());
builder.append(request.getMethod()).append(" ").append(uri);
String query = request.getHttpURI().getQuery();
if (query != null)
builder.append("?").append(query);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = httpRequest.getInputStream();
InputStream input = Content.Source.asInputStream(request);
int read;
while ((read = input.read()) >= 0)
{
@ -168,18 +173,29 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
}
baos.close();
byte[] bytes = baos.toByteArray();
ServletOutputStream output = httpResponse.getOutputStream();
if (bytes.length == 0)
output.print(builder.toString());
{
Content.Sink.write(response, true, builder.toString(), callback);
}
else
output.println(builder.toString());
output.write(bytes);
{
builder.append("\r\n");
Callback.Completable completable = new Callback.Completable();
Content.Sink.write(response, false, builder.toString(), completable);
completable.whenComplete((r, x) ->
{
if (x != null)
callback.failed(x);
else
response.write(true, ByteBuffer.wrap(bytes), callback);
});
}
}
else
{
throw new ServletException();
}*/
}
}
}
}

View File

@ -28,11 +28,11 @@ import java.util.Locale;
import java.util.concurrent.ConcurrentMap;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
@ -47,6 +47,7 @@ import org.junit.jupiter.api.Test;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -80,12 +81,13 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(socket.getInputStream()));
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
@Test
public void testCONNECTwithIPv6() throws Exception
public void testCONNECTWithIPv6() throws Exception
{
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
String hostPort = "[::1]:" + serverConnector.getLocalPort();
@ -102,6 +104,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(socket.getInputStream()));
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
@ -125,6 +128,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -135,6 +139,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -163,6 +168,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 403 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus());
// Socket should be closed
@ -185,6 +191,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -195,6 +202,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -223,6 +231,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 403 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus());
// Socket should be closed
@ -245,6 +254,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -255,6 +265,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -302,6 +313,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 407 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407, response.getStatus());
assertTrue(response.contains("Proxy-Authenticate".toLowerCase(Locale.ENGLISH)));
@ -327,6 +339,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -337,6 +350,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -350,13 +364,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
try
{
InetAddress address = InetAddress.getByName(invalidHostname);
StringBuilder err = new StringBuilder();
err.append("DNS Hijacking detected: ");
err.append(invalidHostname).append(" should have not returned a valid IP address [");
err.append(address.getHostAddress()).append("]. ");
err.append("Fix your DNS provider to have this test pass.");
err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking");
assertNull(address, err.toString());
String err = """
DNS Hijacking detected: %s should have not returned a valid IP address [%s].
Fix your DNS provider to have this test pass.
For more info see https://en.wikipedia.org/wiki/DNS_hijacking")
""".formatted(invalidHostname, address.getHostAddress());
assertNull(address, err);
}
catch (UnknownHostException e)
{
@ -380,6 +393,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 500 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, response.getStatus(), "Response Code");
}
}
@ -403,6 +417,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -413,6 +428,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -440,10 +456,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
// The pipelined request must have gone up to the server as is
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -468,6 +486,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
for (int i = 0; i < 10; ++i)
@ -480,6 +499,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -505,6 +525,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -515,6 +536,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
@ -545,6 +567,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -578,6 +601,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -590,6 +614,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("POST /echo\r\nHELLO", response.getContent());
@ -601,6 +626,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -634,14 +660,11 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
StringBuilder body = new StringBuilder();
String chunk = "0123456789ABCDEF";
for (int i = 0; i < 1024 * 1024; ++i)
{
body.append(chunk);
}
String body = chunk.repeat(1024 * 1024);
request =
"POST /echo HTTP/1.1\r\n" +
@ -653,6 +676,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("POST /echo\r\n" + body, response.getContent());
}
@ -676,14 +700,14 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
}
@Override
protected void connectToServer(HttpServletRequest request, String host, int port, Promise<SocketChannel> promise)
protected void connectToServer(Request request, String host, int port, Promise<SocketChannel> promise)
{
assertEquals(contextValue, request.getAttribute(contextKey));
super.connectToServer(request, host, port, promise);
}
@Override
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
protected void prepareContext(Request request, ConcurrentMap<String, Object> context)
{
// Transfer data from the HTTP request to the connection context
assertEquals(contextValue, request.getAttribute(contextKey));
@ -722,6 +746,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
String body = "0123456789ABCDEF";
@ -735,6 +760,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("POST /echo\r\n" + body, response.getContent());
}
@ -763,10 +789,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
// The pipelined request must have gone up to the server as is
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -791,6 +819,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -803,6 +832,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// The pipelined request must have gone up to the server as is
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -813,10 +843,10 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
String cp = request.getContext().getContextPath();
String cp = request.getPathInContext();
switch (cp)
{
case "/echo":
case "/echo" ->
{
StringBuilder builder = new StringBuilder();
builder.append(request.getMethod()).append(" ").append(cp);
@ -836,24 +866,29 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
byte[] bytes = baos.toByteArray();
if (bytes.length == 0)
{
Content.Sink.write(response, true, builder.toString(), callback);
}
else
{
Content.Sink.write(response, false, builder.toString(), callback);
Content.Sink.write(response, true, "/n" + bytes, callback);
builder.append("\r\n");
Callback.Completable completable = new Callback.Completable();
Content.Sink.write(response, false, builder.toString(), completable);
completable.whenComplete((r, x) ->
{
if (x != null)
callback.failed(x);
else
response.write(true, ByteBuffer.wrap(bytes), callback);
});
}
break;
}
case "/close":
case "/close" ->
{
callback.succeeded();
request.getConnectionMetaData().getConnection().getEndPoint().close();
break;
}
default:
{
throw new ServletException();
callback.succeeded();
}
default -> throw new ServletException();
}
}
}

View File

@ -18,7 +18,6 @@ import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
@ -34,13 +33,13 @@ import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@ -184,10 +183,11 @@ public class ForwardProxyServerTest
else
assertFalse(request.contains("https://"));
String response =
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
String response = """
HTTP/1.1 200 OK
Content-Length: 0
""";
getEndPoint().write(Callback.NOOP, ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
}
catch (Throwable x)
@ -233,16 +233,14 @@ public class ForwardProxyServerTest
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
startServer(null, http, new EmptyServerHandler()
startServer(null, http, new Handler.Processor()
{
@Override
public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
//TODO fix me
/* String remoteHost = request.getRemoteHost();
String remoteHost = org.eclipse.jetty.server.Request.getRemoteAddr(request);
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
String remoteAddr = request.getRemoteAddr();
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));*/
callback.succeeded();
}
});
startProxy(new ProxyServlet()
@ -273,16 +271,14 @@ public class ForwardProxyServerTest
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
startServer(null, http, new EmptyServerHandler()
startServer(null, http, new Handler.Processor()
{
@Override
public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
//TODO fixme
/* String remoteHost = request.getRemoteHost();
String remoteHost = org.eclipse.jetty.server.Request.getRemoteAddr(request);
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
String remoteAddr = request.getRemoteAddr();
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));*/
callback.succeeded();
}
});
startProxy(new ProxyServlet()

View File

@ -13,14 +13,15 @@
package org.eclipse.jetty.ee10.proxy;
import java.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Principal;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -31,9 +32,6 @@ import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.Origin;
@ -50,15 +48,18 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.FutureFormFields;
import org.eclipse.jetty.server.Handler;
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.server.internal.HttpConnection;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -349,18 +350,16 @@ public class ForwardProxyTLSServerTest
@Override
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
{
//TODO fixme
/*
try
{
// Make sure the proxy remains idle enough.
sleep(2 * idleTimeout);
super.handleConnect(request, request, response, serverAddress);
}
catch (Throwable x)
{
onConnectFailure(request, response, null, x);
}*/
try
{
// Make sure the proxy remains idle enough.
sleep(2 * idleTimeout);
super.handleConnect(request, response, callback, serverAddress);
}
catch (Throwable x)
{
onConnectFailure(request, response, callback, x);
}
}
});
@ -458,8 +457,7 @@ public class ForwardProxyTLSServerTest
@Override
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
{
//TODO fix me
/*request.getHttpChannel().getHttpTransport()).close();*/
request.getConnectionMetaData().getConnection().getEndPoint().close();
}
});
@ -513,20 +511,21 @@ public class ForwardProxyTLSServerTest
String realm = "test-realm";
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
//TODO fix me
/* @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
@Override
public Request.Processor handle(Request request) throws Exception
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
String proxyAuth = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
return;
return (req, res, cbk) ->
{
res.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
res.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
cbk.succeeded();
};
}
super.handle(target, baseRequest, request, response);
}*/
return super.handle(request);
}
}, realm);
}
@ -537,21 +536,21 @@ public class ForwardProxyTLSServerTest
String realm = "test-realm";
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
//TODO fix me
/*@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
@Override
public Request.Processor handle(Request request) throws Exception
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
String proxyAuth = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
response.getOutputStream().write(new byte[4096]);
return;
return (req, res, cbk) ->
{
res.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
res.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
res.write(true, ByteBuffer.allocate(4096), cbk);
};
}
super.handle(target, baseRequest, request, response);
}*/
return super.handle(request);
}
}, realm);
}
@ -562,21 +561,21 @@ public class ForwardProxyTLSServerTest
String realm = "test-realm";
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
//TODO fix me
/* @Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
@Override
public Request.Processor handle(Request request) throws Exception
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
String proxyAuth = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
response.getOutputStream().write(new byte[1024]);
return;
return (req, res, cbk) ->
{
res.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
res.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
res.write(true, ByteBuffer.allocate(1024), cbk);
};
}
super.handle(target, baseRequest, request, response);
}*/
return super.handle(request);
}
}, realm, true);
}
@ -587,14 +586,13 @@ public class ForwardProxyTLSServerTest
String realm = "test-realm";
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
//TODO fix me
/* @Override
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
@Override
protected boolean handleAuthentication(Request request, Response response, String address)
{
String header = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.toString());
String header = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (header == null || !header.startsWith("Basic "))
{
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.toString(), "Basic realm=\"" + realm + "\"");
response.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
// Returning false adds Connection: close to the 407 response.
return false;
}
@ -602,7 +600,7 @@ public class ForwardProxyTLSServerTest
{
return true;
}
}*/
}
}, realm);
}
@ -673,9 +671,8 @@ public class ForwardProxyTLSServerTest
for (int i = 0; i < keyManagers.length; i++)
{
KeyManager keyManager = keyManagers[i];
if (keyManager instanceof X509ExtendedKeyManager)
if (keyManager instanceof X509ExtendedKeyManager extKeyManager)
{
X509ExtendedKeyManager extKeyManager = (X509ExtendedKeyManager)keyManager;
keyManagers[i] = new X509ExtendedKeyManagerWrapper(extKeyManager)
{
@Override
@ -889,18 +886,42 @@ public class ForwardProxyTLSServerTest
@Override
public void process(Request request, Response response, Callback callback) throws Exception
{
//TODO fix me
/* String uri = httpRequest.getRequestURI();
String uri = request.getPathInContext();
if ("/echo".equals(uri))
{
String body = httpRequest.getParameter("body");
ServletOutputStream output = httpResponse.getOutputStream();
output.print(body);
if (request.getHttpURI().getQuery() != null)
{
Fields fields = Request.extractQueryParameters(request);
String body = fields.getValue("body");
if (body != null)
Content.Sink.write(response, true, body, callback);
else
callback.succeeded();
}
else if (MimeTypes.Type.FORM_ENCODED.is(request.getHeaders().get(HttpHeader.CONTENT_TYPE)))
{
CompletableFuture<Fields> completable = FutureFormFields.forRequest(request);
completable.whenComplete((fields, failure) ->
{
if (failure != null)
{
callback.failed(failure);
}
else
{
String body = fields.getValue("body");
if (body != null)
Content.Sink.write(response, true, body, callback);
else
callback.succeeded();
}
});
}
}
else
{
throw new ServletException();
}*/
}
}
}
}

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.ee10.proxy;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;

View File

@ -46,6 +46,7 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -53,6 +54,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -178,6 +180,7 @@ public class ProxyServletFailureTest
@ParameterizedTest
@MethodSource("impls")
@Disabled("idle timeouts do not work yet")
public void testClientRequestDoesNotSendContentProxyIdlesTimeout(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
prepareProxy(proxyServletClass);
@ -203,6 +206,7 @@ public class ProxyServletFailureTest
socket.setSoTimeout(2 * idleTimeout);
HttpTester.Response response = HttpTester.parseResponse(socket.getInputStream());
assertNotNull(response);
assertThat("response status", response.getStatus(), greaterThanOrEqualTo(500));
String connectionHeader = response.get(HttpHeader.CONNECTION);
assertNotNull(connectionHeader);
@ -213,6 +217,7 @@ public class ProxyServletFailureTest
@ParameterizedTest
@MethodSource("impls")
@Disabled("idle timeouts do not work yet")
public void testClientRequestStallsContentProxyIdlesTimeout(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
prepareProxy(proxyServletClass);
@ -239,6 +244,7 @@ public class ProxyServletFailureTest
socket.setSoTimeout(2 * idleTimeout);
HttpTester.Response response = HttpTester.parseResponse(socket.getInputStream());
assertNotNull(response);
assertThat("response status", response.getStatus(), greaterThanOrEqualTo(500));
String connectionHeader = response.get(HttpHeader.CONNECTION);
assertNotNull(connectionHeader);
@ -249,6 +255,7 @@ public class ProxyServletFailureTest
@ParameterizedTest
@MethodSource("impls")
@Disabled("idle timeouts do not work yet")
public void testProxyRequestStallsContentServerIdlesTimeout(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
@ -305,15 +312,11 @@ public class ProxyServletFailureTest
long idleTimeout = 1000;
serverConnector.setIdleTimeout(idleTimeout);
//TODO fix me
/* try (StacklessLogging ignore = new StacklessLogging(HttpChannelState.class))
{
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.body(new BytesRequestContent(content))
.send();
assertThat(response.toString(), response.getStatus(), is(expected));
}*/
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.body(new BytesRequestContent(content))
.send();
assertThat(response.toString(), response.getStatus(), is(expected));
}
@ParameterizedTest
@ -399,24 +402,20 @@ public class ProxyServletFailureTest
@MethodSource("impls")
public void testServerException(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
//TODO fix me
/* try (StacklessLogging ignore = new StacklessLogging(HttpChannelState.class))
prepareProxy(proxyServletClass);
prepareServer(new HttpServlet()
{
prepareProxy(proxyServletClass);
prepareServer(new HttpServlet()
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException
{
throw new ServletException("Expected Test Exception");
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(500, response.getStatus());
}*/
throw new ServletException("Expected Test Exception");
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(500, response.getStatus());
}
}

View File

@ -107,6 +107,7 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -610,6 +611,7 @@ public class ProxyServletTest
{
// Make sure the proxy coalesced the Via headers into just one.
ServletContextRequest servletContextRequest = ServletContextRequest.getBaseRequest(request);
assertNotNull(servletContextRequest);
assertEquals(1, servletContextRequest.getHeaders().getFields(HttpHeader.VIA).size());
PrintWriter writer = response.getWriter();
List<String> viaValues = Collections.list(request.getHeaders("Via"));
@ -622,7 +624,7 @@ public class ProxyServletTest
String existingViaHeader = "1.0 charon";
ContentResponse response = client.newRequest("http://localhost:" + serverConnector.getLocalPort())
.header(HttpHeader.VIA, existingViaHeader)
.headers(headers -> headers.put(HttpHeader.VIA, existingViaHeader))
.send();
String expected = String.join(", ", existingViaHeader, "1.1 " + viaHost);
assertThat(response.getContentAsString(), equalTo(expected));

View File

@ -1,6 +1,5 @@
# Jetty Logging using jetty-slf4j-impl
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.proxy.LEVEL=DEBUG
#org.eclipse.jetty.ee10.proxy.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.server.HttpInput.LEVEL=DEBUG
#org.eclipse.jetty.server.ConnectHandler.LEVEL=DEBUG

View File

@ -62,6 +62,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ResourceContentFactory;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.TunnelSupport;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.Blocker;
@ -504,6 +505,12 @@ public class DefaultServlet extends HttpServlet
return _coreRequest.read();
}
@Override
public boolean isPushSupported()
{
return _coreRequest.isPushSupported();
}
@Override
public void push(MetaData.Request request)
{
@ -516,10 +523,15 @@ public class DefaultServlet extends HttpServlet
return false;
}
@Override
public TunnelSupport getTunnelSupport()
{
return _coreRequest.getTunnelSupport();
}
@Override
public void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper)
{
}
@Override

View File

@ -375,7 +375,6 @@ public class HttpInput extends ServletInputStream implements Runnable
if (chunk.isTerminal())
{
if (chunk instanceof Content.Chunk.Error errorChunk)
{
Throwable error = errorChunk.getCause();

View File

@ -433,7 +433,7 @@ public class ServletChannel implements Runnable
try
{
// Get ready to send an error response
getResponse().reset();
getResponse().resetContent();
// the following is needed as you cannot trust the response code and reason
// as those could have been modified after calling sendError

View File

@ -56,6 +56,7 @@ import jakarta.servlet.http.PushBuilder;
import org.eclipse.jetty.ee10.servlet.security.Authentication;
import org.eclipse.jetty.ee10.servlet.security.UserIdentity;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@ -452,8 +453,23 @@ public class ServletContextRequest extends ContextRequest implements Runnable
@Override
public Cookie[] getCookies()
{
// TODO
return new Cookie[0];
// TODO: optimize this.
return Request.getCookies(getRequest()).stream()
.map(this::convertCookie)
.toArray(Cookie[]::new);
}
public Cookie convertCookie(HttpCookie cookie)
{
Cookie result = new Cookie(cookie.getName(), cookie.getValue());
// TODO: inbound (client-to-server) cookies don't have all these parameters.
// result.setPath(cookie.getPath());
// result.setDomain(cookie.getDomain());
// result.setSecure(cookie.isSecure());
// result.setHttpOnly(cookie.isHttpOnly());
// result.setMaxAge((int)cookie.getMaxAge());
// TODO: sameSite?
return result;
}
@Override
@ -573,7 +589,7 @@ public class ServletContextRequest extends ContextRequest implements Runnable
@Override
public StringBuffer getRequestURL()
{
return new StringBuffer(ServletContextRequest.this.getHttpURI().asString());
return new StringBuffer(HttpURI.build(ServletContextRequest.this.getHttpURI()).query(null).asString());
}
@Override

View File

@ -77,6 +77,7 @@ import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.TunnelSupport;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.BufferUtil;
@ -2287,6 +2288,12 @@ public class RequestTest
{
}
@Override
public boolean isPushSupported()
{
return false;
}
@Override
public void push(MetaData.Request request)
{
@ -2298,6 +2305,12 @@ public class RequestTest
return false;
}
@Override
public TunnelSupport getTunnelSupport()
{
return null;
}
@Override
public void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper)
{

View File

@ -60,6 +60,7 @@ import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.TunnelSupport;
import org.eclipse.jetty.session.DefaultSessionCache;
import org.eclipse.jetty.session.DefaultSessionIdManager;
import org.eclipse.jetty.session.NullSessionDataStore;
@ -2349,6 +2350,12 @@ public class ResponseTest
{
}
@Override
public boolean isPushSupported()
{
return false;
}
@Override
public void push(MetaData.Request request)
{
@ -2360,6 +2367,12 @@ public class ResponseTest
return false;
}
@Override
public TunnelSupport getTunnelSupport()
{
return null;
}
@Override
public void addHttpStreamWrapper(Function<HttpStream, HttpStream.Wrapper> wrapper)
{

View File

@ -45,6 +45,7 @@ import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CountingCallback;
@ -229,7 +230,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
/**
* <p>Convenience extension of {@link AsyncMiddleManServlet} that offers transparent proxy functionalities.</p>
*
* @see org.eclipse.jetty.proxy.AbstractProxyServlet.TransparentDelegate
* @see AbstractProxyServlet.TransparentDelegate
*/
public static class Transparent extends AsyncMiddleManServlet
{
@ -370,7 +371,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
for (ByteBuffer buffer : buffers)
{
newContentBytes += buffer.remaining();
this.content.offer(buffer, counter);
this.content.write(buffer, counter);
}
buffers.clear();
}
@ -844,11 +845,11 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
}
@Override
public boolean offer(ByteBuffer buffer, Callback callback)
public void write(ByteBuffer buffer, Callback callback)
{
if (_log.isDebugEnabled())
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(clientRequest), buffer.remaining());
return super.offer(buffer, callback);
super.write(buffer, callback);
}
}

View File

@ -28,6 +28,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
@ -98,7 +99,7 @@ public class AsyncProxyServlet extends ProxyServlet
/**
* <p>Convenience extension of {@link AsyncProxyServlet} that offers transparent proxy functionalities.</p>
*
* @see org.eclipse.jetty.proxy.AbstractProxyServlet.TransparentDelegate
* @see AbstractProxyServlet.TransparentDelegate
*/
public static class Transparent extends AsyncProxyServlet
{
@ -187,7 +188,7 @@ public class AsyncProxyServlet extends ProxyServlet
protected void onRequestContent(HttpServletRequest request, Request proxyRequest, AsyncRequestContent content, byte[] buffer, int offset, int length, Callback callback)
{
content.offer(ByteBuffer.wrap(buffer, offset, length), callback);
content.write(ByteBuffer.wrap(buffer, offset, length), callback);
}
@Override

View File

@ -1,656 +0,0 @@
//
// ========================================================================
// 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.ee9.proxy;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>Implementation of a {@link Handler} that supports HTTP CONNECT.</p>
*/
public class ConnectHandler extends HandlerWrapper
{
protected static final Logger LOG = LoggerFactory.getLogger(ConnectHandler.class);
private final Set<String> whiteList = new HashSet<>();
private final Set<String> blackList = new HashSet<>();
private Executor executor;
private Scheduler scheduler;
private ByteBufferPool bufferPool;
private SelectorManager selector;
private long connectTimeout = 15000;
private long idleTimeout = 30000;
private int bufferSize = 4096;
public ConnectHandler()
{
this(null);
}
public ConnectHandler(Handler handler)
{
setHandler(handler);
}
public Executor getExecutor()
{
return executor;
}
public void setExecutor(Executor executor)
{
this.executor = executor;
}
public Scheduler getScheduler()
{
return scheduler;
}
public void setScheduler(Scheduler scheduler)
{
updateBean(this.scheduler, scheduler);
this.scheduler = scheduler;
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public void setByteBufferPool(ByteBufferPool bufferPool)
{
updateBean(this.bufferPool, bufferPool);
this.bufferPool = bufferPool;
}
/**
* @return the timeout, in milliseconds, to connect to the remote server
*/
public long getConnectTimeout()
{
return connectTimeout;
}
/**
* @param connectTimeout the timeout, in milliseconds, to connect to the remote server
*/
public void setConnectTimeout(long connectTimeout)
{
this.connectTimeout = connectTimeout;
}
/**
* @return the idle timeout, in milliseconds
*/
public long getIdleTimeout()
{
return idleTimeout;
}
/**
* @param idleTimeout the idle timeout, in milliseconds
*/
public void setIdleTimeout(long idleTimeout)
{
this.idleTimeout = idleTimeout;
}
public int getBufferSize()
{
return bufferSize;
}
public void setBufferSize(int bufferSize)
{
this.bufferSize = bufferSize;
}
@Override
protected void doStart() throws Exception
{
if (executor == null)
executor = getServer().getThreadPool();
if (scheduler == null)
{
scheduler = getServer().getBean(Scheduler.class);
if (scheduler == null)
scheduler = new ScheduledExecutorScheduler(String.format("Proxy-Scheduler-%x", hashCode()), false);
addBean(scheduler);
}
if (bufferPool == null)
{
bufferPool = new MappedByteBufferPool();
addBean(bufferPool);
}
addBean(selector = newSelectorManager());
selector.setConnectTimeout(getConnectTimeout());
super.doStart();
}
protected SelectorManager newSelectorManager()
{
return new ConnectManager(getExecutor(), getScheduler(), 1);
}
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String tunnelProtocol = jettyRequest.getMetaData().getProtocol();
if (HttpMethod.CONNECT.is(request.getMethod()) && tunnelProtocol == null)
{
String serverAddress = jettyRequest.getHttpURI().getAuthority();
if (LOG.isDebugEnabled())
LOG.debug("CONNECT request for {}", serverAddress);
handleConnect(jettyRequest, request, response, serverAddress);
}
else
{
super.handle(target, jettyRequest, request, response);
}
}
/**
* <p>Handles a CONNECT request.</p>
* <p>CONNECT requests may have authentication headers such as {@code Proxy-Authorization}
* that authenticate the client with the proxy.</p>
*
* @param baseRequest Jetty-specific http request
* @param request the http request
* @param response the http response
* @param serverAddress the remote server address in the form {@code host:port}
*/
protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
{
baseRequest.setHandled(true);
try
{
boolean proceed = handleAuthentication(request, response, serverAddress);
if (!proceed)
{
if (LOG.isDebugEnabled())
LOG.debug("Missing proxy authentication");
sendConnectResponse(request, response, HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
return;
}
HostPort hostPort = new HostPort(serverAddress);
String host = hostPort.getHost();
int port = hostPort.getPort(80);
if (!validateDestination(host, port))
{
if (LOG.isDebugEnabled())
LOG.debug("Destination {}:{} forbidden", host, port);
sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
return;
}
HttpChannel httpChannel = baseRequest.getHttpChannel();
if (!httpChannel.isTunnellingSupported())
{
if (LOG.isDebugEnabled())
LOG.debug("CONNECT not supported for {}", httpChannel);
sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
return;
}
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(0);
if (LOG.isDebugEnabled())
LOG.debug("Connecting to {}:{}", host, port);
connectToServer(request, host, port, new Promise<>()
{
@Override
public void succeeded(SocketChannel channel)
{
ConnectContext connectContext = new ConnectContext(request, response, asyncContext, httpChannel.getTunnellingEndPoint());
if (channel.isConnected())
selector.accept(channel, connectContext);
else
selector.connect(channel, connectContext);
}
@Override
public void failed(Throwable x)
{
onConnectFailure(request, response, asyncContext, x);
}
});
}
catch (Exception x)
{
onConnectFailure(request, response, null, x);
}
}
protected void connectToServer(HttpServletRequest request, String host, int port, Promise<SocketChannel> promise)
{
SocketChannel channel = null;
try
{
channel = SocketChannel.open();
channel.socket().setTcpNoDelay(true);
channel.configureBlocking(false);
InetSocketAddress address = newConnectAddress(host, port);
channel.connect(address);
promise.succeeded(channel);
}
catch (Throwable x)
{
close(channel);
promise.failed(x);
}
}
private void close(Closeable closeable)
{
try
{
if (closeable != null)
closeable.close();
}
catch (Throwable x)
{
LOG.trace("IGNORED", x);
}
}
/**
* Creates the server address to connect to.
*
* @param host The host from the CONNECT request
* @param port The port from the CONNECT request
* @return The InetSocketAddress to connect to.
*/
protected InetSocketAddress newConnectAddress(String host, int port)
{
return new InetSocketAddress(host, port);
}
protected void onConnectSuccess(ConnectContext connectContext, UpstreamConnection upstreamConnection)
{
ConcurrentMap<String, Object> context = connectContext.getContext();
HttpServletRequest request = connectContext.getRequest();
prepareContext(request, context);
EndPoint downstreamEndPoint = connectContext.getEndPoint();
DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context);
downstreamConnection.setInputBufferSize(getBufferSize());
upstreamConnection.setConnection(downstreamConnection);
downstreamConnection.setConnection(upstreamConnection);
if (LOG.isDebugEnabled())
LOG.debug("Connection setup completed: {}<->{}", downstreamConnection, upstreamConnection);
HttpServletResponse response = connectContext.getResponse();
sendConnectResponse(request, response, HttpServletResponse.SC_OK);
upgradeConnection(request, response, downstreamConnection);
connectContext.getAsyncContext().complete();
}
protected void onConnectFailure(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, Throwable failure)
{
if (LOG.isDebugEnabled())
LOG.debug("CONNECT failed", failure);
sendConnectResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
if (asyncContext != null)
asyncContext.complete();
}
private void sendConnectResponse(HttpServletRequest request, HttpServletResponse response, int statusCode)
{
try
{
response.setStatus(statusCode);
response.setContentLength(0);
if (statusCode != HttpServletResponse.SC_OK)
response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
if (LOG.isDebugEnabled())
LOG.debug("CONNECT response sent {} {}", request.getProtocol(), response.getStatus());
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not send CONNECT response", x);
}
}
/**
* <p>Handles the authentication before setting up the tunnel to the remote server.</p>
* <p>The default implementation returns true.</p>
*
* @param request the HTTP request
* @param response the HTTP response
* @param address the address of the remote server in the form {@code host:port}.
* @return true to allow to connect to the remote host, false otherwise
*/
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
{
return true;
}
protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context)
{
return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context);
}
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
{
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext);
}
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
{
}
private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection)
{
// Set the new connection as request attribute so that
// Jetty understands that it has to upgrade the connection.
request.setAttribute(ConnectionMetaData.UPGRADE_CONNECTION_ATTRIBUTE, connection);
if (LOG.isDebugEnabled())
LOG.debug("Upgraded connection to {}", connection);
}
/**
* <p>Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.</p>
*
* @param endPoint the endPoint to read from
* @param buffer the buffer to read data into
* @param context the context information related to the connection
* @return the number of bytes read (possibly 0 since the read is non-blocking)
* or -1 if the channel has been closed remotely
* @throws IOException if the endPoint cannot be read
*/
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException
{
int read = endPoint.fill(buffer);
if (LOG.isDebugEnabled())
LOG.debug("{} read {} bytes", this, read);
return read;
}
/**
* <p>Writes (with non-blocking semantic) the given buffer of data onto the given endPoint.</p>
*
* @param endPoint the endPoint to write to
* @param buffer the buffer to write
* @param callback the completion callback to invoke
* @param context the context information related to the connection
*/
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback, ConcurrentMap<String, Object> context)
{
if (LOG.isDebugEnabled())
LOG.debug("{} writing {} bytes", this, buffer.remaining());
endPoint.write(callback, buffer);
}
public Set<String> getWhiteListHosts()
{
return whiteList;
}
public Set<String> getBlackListHosts()
{
return blackList;
}
/**
* Checks the given {@code host} and {@code port} against whitelist and blacklist.
*
* @param host the host to check
* @param port the port to check
* @return true if it is allowed to connect to the given host and port
*/
public boolean validateDestination(String host, int port)
{
String hostPort = host + ":" + port;
if (!whiteList.isEmpty())
{
if (!whiteList.contains(hostPort))
{
if (LOG.isDebugEnabled())
LOG.debug("Host {}:{} not whitelisted", host, port);
return false;
}
}
if (!blackList.isEmpty())
{
if (blackList.contains(hostPort))
{
if (LOG.isDebugEnabled())
LOG.debug("Host {}:{} blacklisted", host, port);
return false;
}
}
return true;
}
protected class ConnectManager extends SelectorManager
{
protected ConnectManager(Executor executor, Scheduler scheduler, int selectors)
{
super(executor, scheduler, selectors);
}
@Override
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key)
{
SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler());
endPoint.setIdleTimeout(getIdleTimeout());
return endPoint;
}
@Override
public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
if (ConnectHandler.LOG.isDebugEnabled())
ConnectHandler.LOG.debug("Connected to {}", ((SocketChannel)channel).getRemoteAddress());
ConnectContext connectContext = (ConnectContext)attachment;
UpstreamConnection connection = newUpstreamConnection(endpoint, connectContext);
connection.setInputBufferSize(getBufferSize());
return connection;
}
@Override
protected void connectionFailed(SelectableChannel channel, final Throwable ex, final Object attachment)
{
close(channel);
ConnectContext connectContext = (ConnectContext)attachment;
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
}
}
protected static class ConnectContext
{
private final ConcurrentMap<String, Object> context = new ConcurrentHashMap<>();
private final HttpServletRequest request;
private final HttpServletResponse response;
private final AsyncContext asyncContext;
private final EndPoint endPoint;
public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, EndPoint endPoint)
{
this.request = request;
this.response = response;
this.asyncContext = asyncContext;
this.endPoint = endPoint;
}
public ConcurrentMap<String, Object> getContext()
{
return context;
}
public HttpServletRequest getRequest()
{
return request;
}
public HttpServletResponse getResponse()
{
return response;
}
public AsyncContext getAsyncContext()
{
return asyncContext;
}
public EndPoint getEndPoint()
{
return endPoint;
}
}
public class UpstreamConnection extends ProxyConnection
{
private ConnectContext connectContext;
public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectContext connectContext)
{
super(endPoint, executor, bufferPool, connectContext.getContext());
this.connectContext = connectContext;
}
@Override
public void onOpen()
{
super.onOpen();
onConnectSuccess(connectContext, UpstreamConnection.this);
fillInterested();
}
@Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
{
return ConnectHandler.this.read(endPoint, buffer, getContext());
}
@Override
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
{
ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}
}
public class DownstreamConnection extends ProxyConnection implements Connection.UpgradeTo
{
private ByteBuffer buffer;
public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
{
super(endPoint, executor, bufferPool, context);
}
@Override
public void onUpgradeTo(ByteBuffer buffer)
{
this.buffer = buffer;
}
@Override
public void onOpen()
{
super.onOpen();
if (buffer == null)
{
fillInterested();
return;
}
int remaining = buffer.remaining();
write(getConnection().getEndPoint(), buffer, new Callback()
{
@Override
public void succeeded()
{
buffer = null;
if (LOG.isDebugEnabled())
LOG.debug("{} wrote initial {} bytes to server", DownstreamConnection.this, remaining);
fillInterested();
}
@Override
public void failed(Throwable x)
{
buffer = null;
if (LOG.isDebugEnabled())
LOG.debug("{} failed to write initial {} bytes to server", this, remaining, x);
close();
getConnection().close();
}
});
}
@Override
protected int read(EndPoint endPoint, ByteBuffer buffer) throws IOException
{
return ConnectHandler.this.read(endPoint, buffer, getContext());
}
@Override
protected void write(EndPoint endPoint, ByteBuffer buffer, Callback callback)
{
ConnectHandler.this.write(endPoint, buffer, callback, getContext());
}
}
}

View File

@ -1,161 +0,0 @@
//
// ========================================================================
// 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.ee9.proxy;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
import org.slf4j.Logger;
public abstract class ProxyConnection extends AbstractConnection
{
protected static final Logger LOG = ConnectHandler.LOG;
private final IteratingCallback pipe = new ProxyIteratingCallback();
private final ByteBufferPool bufferPool;
private final ConcurrentMap<String, Object> context;
private ProxyConnection connection;
protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
{
super(endp, executor);
this.bufferPool = bufferPool;
this.context = context;
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public ConcurrentMap<String, Object> getContext()
{
return context;
}
public Connection getConnection()
{
return connection;
}
public void setConnection(ProxyConnection connection)
{
this.connection = connection;
}
@Override
public void onFillable()
{
pipe.iterate();
}
protected abstract int read(EndPoint endPoint, ByteBuffer buffer) throws IOException;
protected abstract void write(EndPoint endPoint, ByteBuffer buffer, Callback callback);
protected void close(Throwable failure)
{
getEndPoint().close(failure);
}
@Override
public String toConnectionString()
{
EndPoint endPoint = getEndPoint();
return String.format("%s@%x[l:%s<=>r:%s]",
getClass().getSimpleName(),
hashCode(),
endPoint.getLocalSocketAddress(),
endPoint.getRemoteSocketAddress());
}
private class ProxyIteratingCallback extends IteratingCallback
{
private ByteBuffer buffer;
private int filled;
@Override
protected Action process()
{
buffer = bufferPool.acquire(getInputBufferSize(), true);
try
{
int filled = this.filled = read(getEndPoint(), buffer);
if (LOG.isDebugEnabled())
LOG.debug("{} filled {} bytes", ProxyConnection.this, filled);
if (filled > 0)
{
write(connection.getEndPoint(), buffer, this);
return Action.SCHEDULED;
}
else if (filled == 0)
{
bufferPool.release(buffer);
fillInterested();
return Action.IDLE;
}
else
{
bufferPool.release(buffer);
connection.getEndPoint().shutdownOutput();
return Action.SUCCEEDED;
}
}
catch (IOException x)
{
if (LOG.isDebugEnabled())
LOG.debug("{} could not fill", ProxyConnection.this, x);
bufferPool.release(buffer);
disconnect(x);
return Action.SUCCEEDED;
}
}
@Override
public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("{} wrote {} bytes", ProxyConnection.this, filled);
bufferPool.release(buffer);
super.succeeded();
}
@Override
protected void onCompleteSuccess()
{
}
@Override
protected void onCompleteFailure(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("{} failed to write {} bytes", ProxyConnection.this, filled, x);
bufferPool.release(buffer);
disconnect(x);
}
private void disconnect(Throwable x)
{
ProxyConnection.this.close(x);
connection.close(x);
}
}
}

View File

@ -29,6 +29,8 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.util.Callback;
/**
@ -89,7 +91,7 @@ public class ProxyServlet extends AbstractProxyServlet
try
{
Request.Content content = proxyRequestContent(request, response, proxyRequest);
new DelegatingRequestContent(request, proxyRequest, response, content, delegate);
Content.copy(content, delegate, Callback.from(delegate::close, x -> onClientRequestFailure(request, proxyRequest, response, x)));
}
catch (Throwable failure)
{
@ -153,7 +155,7 @@ public class ProxyServlet extends AbstractProxyServlet
/**
* <p>Convenience extension of {@link ProxyServlet} that offers transparent proxy functionalities.</p>
*
* @see org.eclipse.jetty.proxy.AbstractProxyServlet.TransparentDelegate
* @see AbstractProxyServlet.TransparentDelegate
*/
public static class Transparent extends ProxyServlet
{
@ -258,71 +260,19 @@ public class ProxyServlet extends AbstractProxyServlet
}
@Override
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
public Content.Chunk read()
{
if (_log.isDebugEnabled())
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
return super.onRead(buffer, offset, length);
}
@Override
protected void onReadFailure(Throwable failure)
{
onClientRequestFailure(request, proxyRequest, response, failure);
}
}
private class DelegatingRequestContent implements Request.Content.Consumer
{
private final HttpServletRequest clientRequest;
private final Request proxyRequest;
private final HttpServletResponse proxyResponse;
private final AsyncRequestContent delegate;
private final Request.Content.Subscription subscription;
private DelegatingRequestContent(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, Request.Content content, AsyncRequestContent delegate)
{
this.clientRequest = clientRequest;
this.proxyRequest = proxyRequest;
this.proxyResponse = proxyResponse;
this.delegate = delegate;
this.subscription = content.subscribe(this, true);
this.subscription.demand();
}
@Override
public void onContent(ByteBuffer buffer, boolean last, Callback callback)
{
Callback wrapped = Callback.from(() -> succeeded(callback, last), failure -> failed(callback, failure));
if (buffer.hasRemaining())
Content.Chunk chunk = super.read();
if (chunk instanceof Content.Chunk.Error error)
{
delegate.offer(buffer, wrapped);
onClientRequestFailure(request, proxyRequest, response, error.getCause());
}
else
{
wrapped.succeeded();
if (_log.isDebugEnabled())
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), chunk.remaining());
}
if (last)
delegate.close();
}
private void succeeded(Callback callback, boolean last)
{
callback.succeeded();
if (!last)
subscription.demand();
}
private void failed(Callback callback, Throwable failure)
{
callback.failed(failure);
onFailure(failure);
}
@Override
public void onFailure(Throwable failure)
{
onClientRequestFailure(clientRequest, proxyRequest, proxyResponse, failure);
return chunk;
}
}
}

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.ee9.proxy;
import java.io.IOException;
import java.net.Socket;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.jupiter.api.AfterEach;

View File

@ -66,15 +66,16 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ajax.JSON;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
@ -396,7 +397,7 @@ public class AsyncMiddleManServletTest
.body(content)
.send(listener);
byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
content.offer(ByteBuffer.wrap(gzip(bytes)));
content.write(ByteBuffer.wrap(gzip(bytes)), Callback.NOOP);
sleep(1000);
content.close();
@ -557,9 +558,9 @@ public class AsyncMiddleManServletTest
latch.countDown();
});
content.offer(ByteBuffer.allocate(512));
content.write(ByteBuffer.allocate(512), Callback.NOOP);
sleep(1000);
content.offer(ByteBuffer.allocate(512));
content.write(ByteBuffer.allocate(512), Callback.NOOP);
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
@ -770,9 +771,9 @@ public class AsyncMiddleManServletTest
if (result.getResponse().getStatus() == 500)
latch.countDown();
});
content.offer(ByteBuffer.allocate(512));
content.write(ByteBuffer.allocate(512), Callback.NOOP);
sleep(1000);
content.offer(ByteBuffer.allocate(512));
content.write(ByteBuffer.allocate(512), Callback.NOOP);
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
@ -781,40 +782,37 @@ public class AsyncMiddleManServletTest
@Test
public void testClientRequestReadFailsOnSecondRead() throws Exception
{
try (StacklessLogging ignored = new StacklessLogging(HttpChannelState.class))
startServer(new EchoHttpServlet());
startProxy(new AsyncMiddleManServlet()
{
startServer(new EchoHttpServlet());
startProxy(new AsyncMiddleManServlet()
private int count;
@Override
protected int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
{
private int count;
if (++count < 2)
return super.readClientRequestContent(input, buffer);
else
throw new IOException("explicitly_thrown_by_test");
}
});
startClient();
@Override
protected int readClientRequestContent(ServletInputStream input, byte[] buffer) throws IOException
{
if (++count < 2)
return super.readClientRequestContent(input, buffer);
else
throw new IOException("explicitly_thrown_by_test");
}
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == 502)
latch.countDown();
});
startClient();
content.write(ByteBuffer.allocate(512), Callback.NOOP);
sleep(1000);
content.write(ByteBuffer.allocate(512), Callback.NOOP);
content.close();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == 502)
latch.countDown();
});
content.offer(ByteBuffer.allocate(512));
sleep(1000);
content.offer(ByteBuffer.allocate(512));
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
@ -1108,6 +1106,7 @@ public class AsyncMiddleManServletTest
}
@Test
@Disabled("idle timeouts do not work yet")
public void testAfterContentTransformerClosingFilesOnClientRequestException() throws Exception
{
Path targetTestsDir = workDir.getEmptyPathDir();
@ -1372,12 +1371,12 @@ public class AsyncMiddleManServletTest
// Send one chunk of content, the proxy request must not be sent.
ByteBuffer chunk1 = ByteBuffer.allocate(1024);
content.offer(chunk1);
content.write(chunk1, Callback.NOOP);
assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
// Send another chunk of content, the proxy request must not be sent.
ByteBuffer chunk2 = ByteBuffer.allocate(512);
content.offer(chunk2);
content.write(chunk2, Callback.NOOP);
assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
// Finish the content, request must be sent.
@ -1420,12 +1419,12 @@ public class AsyncMiddleManServletTest
// Send one chunk of content, the proxy request must not be sent.
ByteBuffer chunk1 = ByteBuffer.allocate(1024);
content.offer(chunk1);
content.write(chunk1, Callback.NOOP);
assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
// Send another chunk of content, the proxy request must not be sent.
ByteBuffer chunk2 = ByteBuffer.allocate(512);
content.offer(chunk2);
content.write(chunk2, Callback.NOOP);
assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
// Finish the content, request must be sent.
@ -1491,12 +1490,12 @@ public class AsyncMiddleManServletTest
// Send one chunk of content, the proxy request must not be sent.
ByteBuffer chunk1 = ByteBuffer.allocate(1024);
content.offer(chunk1);
content.write(chunk1, Callback.NOOP);
assertFalse(proxyRequestLatch.await(1, TimeUnit.SECONDS));
// Send another chunk of content, the proxy request must be sent.
ByteBuffer chunk2 = ByteBuffer.allocate(512);
content.offer(chunk2);
content.write(chunk2, Callback.NOOP);
assertTrue(proxyRequestLatch.await(5, TimeUnit.SECONDS));
// Finish the content.

View File

@ -100,7 +100,7 @@ public class BalancerServletTest
{
DefaultSessionIdManager sessionIdManager = new DefaultSessionIdManager(server);
sessionIdManager.setWorkerName(nodeName);
server.setSessionIdManager(sessionIdManager);
server.addBean(sessionIdManager, true);
}
return server;
@ -173,7 +173,6 @@ public class BalancerServletTest
RewriteHandler rewrite = new RewriteHandler();
rewrite.setHandler(balancer.getHandler());
balancer.setHandler(rewrite);
rewrite.setRewriteRequestURI(true);
rewrite.addRule(new VirtualHostRuleContainer());
balancer.start();

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee9.proxy;
import java.io.IOException;
import java.security.KeyStore;
import java.security.Principal;
import java.security.cert.X509Certificate;
@ -31,9 +30,7 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.security.auth.x500.X500Principal;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpDestination;
@ -47,15 +44,18 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
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.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -134,18 +134,17 @@ public class ClientAuthProxyTest
private void startServer() throws Exception
{
startServer(new EmptyServerHandler()
startServer(new Handler.Processor()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
public void process(org.eclipse.jetty.server.Request request, Response response, Callback callback)
{
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE);
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
Assertions.assertNotNull(certificates);
X509Certificate certificate = certificates[0];
X500Principal principal = certificate.getSubjectX500Principal();
ServletOutputStream output = response.getOutputStream();
output.println(principal.toString());
output.println(request.getRemotePort());
String body = "%s\r\n%d\r\n".formatted(principal.toString(), org.eclipse.jetty.server.Request.getRemotePort(request));
Content.Sink.write(response, true, body, callback);
}
});
}
@ -212,7 +211,7 @@ public class ClientAuthProxyTest
private static String retrieveUser(HttpServletRequest request)
{
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.JAKARTA_SERVLET_REQUEST_X_509_CERTIFICATE);
X509Certificate[] certificates = (X509Certificate[])request.getAttribute(SecureRequestCustomizer.CERTIFICATES);
String clientName = certificates[0].getSubjectX500Principal().getName();
Matcher matcher = Pattern.compile("CN=([^,]+)").matcher(clientName);
if (matcher.find())

View File

@ -14,26 +14,26 @@
package org.eclipse.jetty.ee9.proxy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
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.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -150,23 +150,22 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
return sslSocket;
}
private static class ServerHandler extends AbstractHandler
private static class ServerHandler extends Handler.Processor
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
public void process(Request request, Response response, Callback callback) throws Exception
{
request.setHandled(true);
String uri = httpRequest.getRequestURI();
String uri = request.getPathInContext();
if ("/echo".equals(uri))
{
StringBuilder builder = new StringBuilder();
builder.append(httpRequest.getMethod()).append(" ").append(uri);
if (httpRequest.getQueryString() != null)
builder.append("?").append(httpRequest.getQueryString());
builder.append(request.getMethod()).append(" ").append(uri);
String query = request.getHttpURI().getQuery();
if (query != null)
builder.append("?").append(query);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = httpRequest.getInputStream();
InputStream input = Content.Source.asInputStream(request);
int read;
while ((read = input.read()) >= 0)
{
@ -175,12 +174,23 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
baos.close();
byte[] bytes = baos.toByteArray();
ServletOutputStream output = httpResponse.getOutputStream();
if (bytes.length == 0)
output.print(builder.toString());
{
Content.Sink.write(response, true, builder.toString(), callback);
}
else
output.println(builder.toString());
output.write(bytes);
{
builder.append("\r\n");
Callback.Completable completable = new Callback.Completable();
Content.Sink.write(response, false, builder.toString(), completable);
completable.whenComplete((r, x) ->
{
if (x != null)
callback.failed(x);
else
response.write(true, ByteBuffer.wrap(bytes), callback);
});
}
}
else
{

View File

@ -28,16 +28,16 @@ import java.util.Locale;
import java.util.concurrent.ConcurrentMap;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Handler;
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.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
@ -47,6 +47,7 @@ import org.junit.jupiter.api.Test;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -80,12 +81,13 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(socket.getInputStream()));
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
@Test
public void testCONNECTwithIPv6() throws Exception
public void testCONNECTWithIPv6() throws Exception
{
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
String hostPort = "[::1]:" + serverConnector.getLocalPort();
@ -102,6 +104,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(socket.getInputStream()));
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
@ -125,6 +128,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -135,6 +139,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -163,6 +168,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 403 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus());
// Socket should be closed
@ -185,6 +191,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -195,6 +202,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -223,6 +231,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 403 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.FORBIDDEN_403, response.getStatus());
// Socket should be closed
@ -245,6 +254,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -255,6 +265,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -267,12 +278,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
connectHandler = new ConnectHandler()
{
@Override
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
protected boolean handleAuthentication(Request request, Response response, String address)
{
String proxyAuthorization = request.getHeader("Proxy-Authorization");
String proxyAuthorization = request.getHeaders().get("Proxy-Authorization");
if (proxyAuthorization == null)
{
response.setHeader("Proxy-Authenticate", "Basic realm=\"test\"");
response.getHeaders().put("Proxy-Authenticate", "Basic realm=\"test\"");
return false;
}
String b64 = proxyAuthorization.substring("Basic ".length());
@ -302,6 +313,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 407 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407, response.getStatus());
assertTrue(response.contains("Proxy-Authenticate".toLowerCase(Locale.ENGLISH)));
@ -327,6 +339,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -337,6 +350,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -350,13 +364,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
try
{
InetAddress address = InetAddress.getByName(invalidHostname);
StringBuilder err = new StringBuilder();
err.append("DNS Hijacking detected: ");
err.append(invalidHostname).append(" should have not returned a valid IP address [");
err.append(address.getHostAddress()).append("]. ");
err.append("Fix your DNS provider to have this test pass.");
err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking");
assertNull(address, err.toString());
String err = """
DNS Hijacking detected: %s should have not returned a valid IP address [%s].
Fix your DNS provider to have this test pass.
For more info see https://en.wikipedia.org/wiki/DNS_hijacking")
""".formatted(invalidHostname, address.getHostAddress());
assertNull(address, err);
}
catch (UnknownHostException e)
{
@ -380,6 +393,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 500 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR_500, response.getStatus(), "Response Code");
}
}
@ -403,6 +417,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -413,6 +428,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -440,10 +456,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
// The pipelined request must have gone up to the server as is
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -468,6 +486,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
for (int i = 0; i < 10; ++i)
@ -480,6 +499,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -505,6 +525,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -515,6 +536,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
@ -545,6 +567,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -578,6 +601,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -590,6 +614,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("POST /echo\r\nHELLO", response.getContent());
@ -601,6 +626,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -634,14 +660,11 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
StringBuilder body = new StringBuilder();
String chunk = "0123456789ABCDEF";
for (int i = 0; i < 1024 * 1024; ++i)
{
body.append(chunk);
}
String body = chunk.repeat(1024 * 1024);
request =
"POST /echo HTTP/1.1\r\n" +
@ -653,6 +676,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("POST /echo\r\n" + body, response.getContent());
}
@ -669,21 +693,21 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
proxy.setHandler(new ConnectHandler()
{
@Override
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
protected boolean handleAuthentication(Request request, Response response, String address)
{
request.setAttribute(contextKey, contextValue);
return super.handleAuthentication(request, response, address);
}
@Override
protected void connectToServer(HttpServletRequest request, String host, int port, Promise<SocketChannel> promise)
protected void connectToServer(Request request, String host, int port, Promise<SocketChannel> promise)
{
assertEquals(contextValue, request.getAttribute(contextKey));
super.connectToServer(request, host, port, promise);
}
@Override
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
protected void prepareContext(Request request, ConcurrentMap<String, Object> context)
{
// Transfer data from the HTTP request to the connection context
assertEquals(contextValue, request.getAttribute(contextKey));
@ -722,6 +746,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
String body = "0123456789ABCDEF";
@ -735,6 +760,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush();
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("POST /echo\r\n" + body, response.getContent());
}
@ -763,10 +789,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
// The pipelined request must have gone up to the server as is
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
@ -791,6 +819,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// Expect 200 OK from the CONNECT request
HttpTester.Input in = HttpTester.from(input);
HttpTester.Response response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
request =
@ -803,30 +832,31 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// The pipelined request must have gone up to the server as is
response = HttpTester.parseResponse(in);
assertNotNull(response);
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals("GET /echo", response.getContent());
}
}
private static class ServerHandler extends AbstractHandler
private static class ServerHandler extends Handler.Processor
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
public void process(Request request, Response response, Callback callback) throws Exception
{
request.setHandled(true);
String uri = httpRequest.getRequestURI();
switch (uri)
String cp = request.getPathInContext();
switch (cp)
{
case "/echo":
case "/echo" ->
{
StringBuilder builder = new StringBuilder();
builder.append(httpRequest.getMethod()).append(" ").append(uri);
if (httpRequest.getQueryString() != null)
builder.append("?").append(httpRequest.getQueryString());
builder.append(request.getMethod()).append(" ").append(cp);
String query = request.getHttpURI().getQuery();
if (query != null)
builder.append("?").append(query);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = httpRequest.getInputStream();
InputStream input = Request.asInputStream(request);
int read;
while ((read = input.read()) >= 0)
{
@ -835,23 +865,30 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
baos.close();
byte[] bytes = baos.toByteArray();
ServletOutputStream output = httpResponse.getOutputStream();
if (bytes.length == 0)
output.print(builder.toString());
{
Content.Sink.write(response, true, builder.toString(), callback);
}
else
output.println(builder.toString());
output.write(bytes);
break;
{
builder.append("\r\n");
Callback.Completable completable = new Callback.Completable();
Content.Sink.write(response, false, builder.toString(), completable);
completable.whenComplete((r, x) ->
{
if (x != null)
callback.failed(x);
else
response.write(true, ByteBuffer.wrap(bytes), callback);
});
}
}
case "/close":
case "/close" ->
{
request.getHttpChannel().getEndPoint().close();
break;
}
default:
{
throw new ServletException();
request.getConnectionMetaData().getConnection().getEndPoint().close();
callback.succeeded();
}
default -> throw new ServletException();
}
}
}

View File

@ -13,24 +13,15 @@
package org.eclipse.jetty.ee9.proxy;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
public class EmptyServerHandler extends AbstractHandler
public class EmptyServerHandler extends Handler.Processor
{
@Override
public final void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
service(target, jettyRequest, request, response);
}
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
public void process(Request request, Response response, Callback callback)
{
}
}

View File

@ -18,7 +18,6 @@ import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
@ -34,6 +33,7 @@ import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
@ -183,10 +183,11 @@ public class ForwardProxyServerTest
else
assertFalse(request.contains("https://"));
String response =
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
String response = """
HTTP/1.1 200 OK
Content-Length: 0
""";
getEndPoint().write(Callback.NOOP, ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
}
catch (Throwable x)
@ -232,15 +233,14 @@ public class ForwardProxyServerTest
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
startServer(null, http, new EmptyServerHandler()
startServer(null, http, new Handler.Processor()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
String remoteHost = jettyRequest.getRemoteHost();
String remoteHost = org.eclipse.jetty.server.Request.getRemoteAddr(request);
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
String remoteAddr = jettyRequest.getRemoteAddr();
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));
callback.succeeded();
}
});
startProxy(new ProxyServlet()
@ -271,15 +271,14 @@ public class ForwardProxyServerTest
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
startServer(null, http, new EmptyServerHandler()
startServer(null, http, new Handler.Processor()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
public void process(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback)
{
String remoteHost = jettyRequest.getRemoteHost();
String remoteHost = org.eclipse.jetty.server.Request.getRemoteAddr(request);
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
String remoteAddr = jettyRequest.getRemoteAddr();
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));
callback.succeeded();
}
});
startProxy(new ProxyServlet()

View File

@ -13,14 +13,15 @@
package org.eclipse.jetty.ee9.proxy;
import java.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Principal;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -31,9 +32,6 @@ import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.Origin;
@ -50,14 +48,18 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.FutureFormFields;
import org.eclipse.jetty.server.Handler;
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.server.handler.AbstractHandler;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
@ -346,17 +348,17 @@ public class ForwardProxyTLSServerTest
startProxy(proxyTLS, new ConnectHandler()
{
@Override
protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
{
try
{
// Make sure the proxy remains idle enough.
sleep(2 * idleTimeout);
super.handleConnect(baseRequest, request, response, serverAddress);
super.handleConnect(request, response, callback, serverAddress);
}
catch (Throwable x)
{
onConnectFailure(request, response, null, x);
onConnectFailure(request, response, callback, x);
}
}
});
@ -453,9 +455,9 @@ public class ForwardProxyTLSServerTest
startProxy(proxyTLS, new ConnectHandler()
{
@Override
protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
protected void handleConnect(Request request, Response response, Callback callback, String serverAddress)
{
((HttpConnection)baseRequest.getHttpChannel().getHttpTransport()).close();
request.getConnectionMetaData().getConnection().getEndPoint().close();
}
});
@ -510,17 +512,19 @@ public class ForwardProxyTLSServerTest
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
public Request.Processor handle(Request request) throws Exception
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
String proxyAuth = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
return;
return (req, res, cbk) ->
{
res.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
res.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
cbk.succeeded();
};
}
super.handle(target, baseRequest, request, response);
return super.handle(request);
}
}, realm);
}
@ -533,18 +537,19 @@ public class ForwardProxyTLSServerTest
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
public Request.Processor handle(Request request) throws Exception
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
String proxyAuth = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
response.getOutputStream().write(new byte[4096]);
return;
return (req, res, cbk) ->
{
res.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
res.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
res.write(true, ByteBuffer.allocate(4096), cbk);
};
}
super.handle(target, baseRequest, request, response);
return super.handle(request);
}
}, realm);
}
@ -557,18 +562,19 @@ public class ForwardProxyTLSServerTest
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
public Request.Processor handle(Request request) throws Exception
{
String proxyAuth = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
String proxyAuth = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (proxyAuth == null)
{
baseRequest.setHandled(true);
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
response.getOutputStream().write(new byte[1024]);
return;
return (req, res, cbk) ->
{
res.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
res.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
res.write(true, ByteBuffer.allocate(1024), cbk);
};
}
super.handle(target, baseRequest, request, response);
return super.handle(request);
}
}, realm, true);
}
@ -581,12 +587,12 @@ public class ForwardProxyTLSServerTest
testProxyAuthentication(proxyTLS, new ConnectHandler()
{
@Override
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
protected boolean handleAuthentication(Request request, Response response, String address)
{
String header = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.toString());
String header = request.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION);
if (header == null || !header.startsWith("Basic "))
{
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.toString(), "Basic realm=\"" + realm + "\"");
response.getHeaders().put(HttpHeader.PROXY_AUTHENTICATE, "Basic realm=\"" + realm + "\"");
// Returning false adds Connection: close to the 407 response.
return false;
}
@ -665,9 +671,8 @@ public class ForwardProxyTLSServerTest
for (int i = 0; i < keyManagers.length; i++)
{
KeyManager keyManager = keyManagers[i];
if (keyManager instanceof X509ExtendedKeyManager)
if (keyManager instanceof X509ExtendedKeyManager extKeyManager)
{
X509ExtendedKeyManager extKeyManager = (X509ExtendedKeyManager)keyManager;
keyManagers[i] = new X509ExtendedKeyManagerWrapper(extKeyManager)
{
@Override
@ -876,19 +881,42 @@ public class ForwardProxyTLSServerTest
}
}
private static class ServerHandler extends AbstractHandler
private static class ServerHandler extends Handler.Processor
{
@Override
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
public void process(Request request, Response response, Callback callback) throws Exception
{
request.setHandled(true);
String uri = httpRequest.getRequestURI();
String uri = request.getPathInContext();
if ("/echo".equals(uri))
{
String body = httpRequest.getParameter("body");
ServletOutputStream output = httpResponse.getOutputStream();
output.print(body);
if (request.getHttpURI().getQuery() != null)
{
Fields fields = Request.extractQueryParameters(request);
String body = fields.getValue("body");
if (body != null)
Content.Sink.write(response, true, body, callback);
else
callback.succeeded();
}
else if (MimeTypes.Type.FORM_ENCODED.is(request.getHeaders().get(HttpHeader.CONTENT_TYPE)))
{
CompletableFuture<Fields> completable = FutureFormFields.forRequest(request);
completable.whenComplete((fields, failure) ->
{
if (failure != null)
{
callback.failed(failure);
}
else
{
String body = fields.getValue("body");
if (body != null)
Content.Sink.write(response, true, body, callback);
else
callback.succeeded();
}
});
}
}
else
{

View File

@ -15,6 +15,7 @@ package org.eclipse.jetty.ee9.proxy;
import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
import org.eclipse.jetty.ee9.servlet.ServletHolder;
import org.eclipse.jetty.server.ConnectHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;

View File

@ -40,14 +40,13 @@ import org.eclipse.jetty.ee9.servlet.ServletContextHandler;
import org.eclipse.jetty.ee9.servlet.ServletHolder;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@ -181,6 +180,7 @@ public class ProxyServletFailureTest
@ParameterizedTest
@MethodSource("impls")
@Disabled("idle timeouts do not work yet")
public void testClientRequestDoesNotSendContentProxyIdlesTimeout(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
prepareProxy(proxyServletClass);
@ -206,6 +206,7 @@ public class ProxyServletFailureTest
socket.setSoTimeout(2 * idleTimeout);
HttpTester.Response response = HttpTester.parseResponse(socket.getInputStream());
assertNotNull(response);
assertThat("response status", response.getStatus(), greaterThanOrEqualTo(500));
String connectionHeader = response.get(HttpHeader.CONNECTION);
assertNotNull(connectionHeader);
@ -216,6 +217,7 @@ public class ProxyServletFailureTest
@ParameterizedTest
@MethodSource("impls")
@Disabled("idle timeouts do not work yet")
public void testClientRequestStallsContentProxyIdlesTimeout(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
prepareProxy(proxyServletClass);
@ -242,6 +244,7 @@ public class ProxyServletFailureTest
socket.setSoTimeout(2 * idleTimeout);
HttpTester.Response response = HttpTester.parseResponse(socket.getInputStream());
assertNotNull(response);
assertThat("response status", response.getStatus(), greaterThanOrEqualTo(500));
String connectionHeader = response.get(HttpHeader.CONNECTION);
assertNotNull(connectionHeader);
@ -270,11 +273,11 @@ public class ProxyServletFailureTest
AsyncRequestContent requestContent = new AsyncRequestContent()
{
@Override
public boolean offer(ByteBuffer buffer, Callback callback)
public void write(ByteBuffer buffer, Callback callback)
{
// Send less content to trigger the test condition.
buffer.limit(buffer.limit() - 1);
return super.offer(buffer.slice(), callback);
super.write(buffer.slice(), callback);
}
};
request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, requestContent));
@ -308,14 +311,11 @@ public class ProxyServletFailureTest
long idleTimeout = 1000;
serverConnector.setIdleTimeout(idleTimeout);
try (StacklessLogging ignore = new StacklessLogging(HttpChannelState.class))
{
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.body(new BytesRequestContent(content))
.send();
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.body(new BytesRequestContent(content))
.send();
assertThat(response.toString(), response.getStatus(), is(expected));
}
assertThat(response.toString(), response.getStatus(), is(expected));
}
@ParameterizedTest
@ -401,23 +401,20 @@ public class ProxyServletFailureTest
@MethodSource("impls")
public void testServerException(Class<? extends ProxyServlet> proxyServletClass) throws Exception
{
try (StacklessLogging ignore = new StacklessLogging(HttpChannelState.class))
prepareProxy(proxyServletClass);
prepareServer(new HttpServlet()
{
prepareProxy(proxyServletClass);
prepareServer(new HttpServlet()
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException
{
throw new ServletException("Expected Test Exception");
}
});
throw new ServletException("Expected Test Exception");
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(500, response.getStatus());
}
assertEquals(500, response.getStatus());
}
}

View File

@ -85,6 +85,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -607,7 +608,7 @@ public class ProxyServletTest
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
// Make sure the proxy coalesced the Via headers into just one.
org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request)request;
var jettyRequest = (org.eclipse.jetty.ee9.nested.Request)request;
assertEquals(1, jettyRequest.getHttpFields().getFields(HttpHeader.VIA).size());
PrintWriter writer = response.getWriter();
List<String> viaValues = Collections.list(request.getHeaders("Via"));
@ -620,7 +621,7 @@ public class ProxyServletTest
String existingViaHeader = "1.0 charon";
ContentResponse response = client.newRequest("http://localhost:" + serverConnector.getLocalPort())
.header(HttpHeader.VIA, existingViaHeader)
.headers(headers -> headers.put(HttpHeader.VIA, existingViaHeader))
.send();
String expected = String.join(", ", existingViaHeader, "1.1 " + viaHost);
assertThat(response.getContentAsString(), equalTo(expected));
@ -1499,7 +1500,7 @@ public class ProxyServletTest
new Random().nextBytes(content);
int chunk1 = content.length / 2;
AsyncRequestContent requestContent = new AsyncRequestContent();
requestContent.offer(ByteBuffer.wrap(content, 0, chunk1));
requestContent.write(ByteBuffer.wrap(content, 0, chunk1), Callback.NOOP);
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))
@ -1522,7 +1523,7 @@ public class ProxyServletTest
// Wait a while and then offer more content.
Thread.sleep(1000);
requestContent.offer(ByteBuffer.wrap(content, chunk1, content.length - chunk1));
requestContent.write(ByteBuffer.wrap(content, chunk1, content.length - chunk1), Callback.NOOP);
requestContent.close();
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
@ -1559,7 +1560,7 @@ public class ProxyServletTest
new Random().nextBytes(content);
int chunk1 = content.length / 2;
AsyncRequestContent requestContent = new AsyncRequestContent();
requestContent.offer(ByteBuffer.wrap(content, 0, chunk1));
requestContent.write(ByteBuffer.wrap(content, 0, chunk1), Callback.NOOP);
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString()))

View File

@ -65,7 +65,7 @@
<module>jetty-ee9-openid</module>
<!-- <module>jetty-ee9-osgi</module>-->
<module>jetty-ee9-plus</module>
<!-- <module>jetty-ee9-proxy</module>-->
<module>jetty-ee9-proxy</module>
<module>jetty-ee9-quickstart</module>
<!-- <module>jetty-ee9-runner</module>-->
<module>jetty-ee9-websocket</module>