Completed port of ConnectHandler and ProxyServlet implementations.

This commit is contained in:
Simone Bordet 2012-11-13 15:33:37 +01:00
commit ba40f7897d
48 changed files with 2979 additions and 3094 deletions

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.client;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.URI; import java.net.URI;
@ -44,6 +43,7 @@ import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.CookieStore; import org.eclipse.jetty.client.api.CookieStore;
import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.ProxyConfiguration;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
@ -127,6 +127,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile long idleTimeout; private volatile long idleTimeout;
private volatile boolean tcpNoDelay = true; private volatile boolean tcpNoDelay = true;
private volatile boolean dispatchIO = true; private volatile boolean dispatchIO = true;
private volatile ProxyConfiguration proxyConfig;
public HttpClient() public HttpClient()
{ {
@ -351,7 +352,7 @@ public class HttpClient extends ContainerLifeCycle
channel.bind(bindAddress); channel.bind(bindAddress);
configure(channel); configure(channel);
channel.configureBlocking(false); channel.configureBlocking(false);
channel.connect(new InetSocketAddress(destination.getHost(), destination.getPort())); channel.connect(destination.getConnectAddress());
Future<Connection> result = new ConnectionCallback(destination, callback); Future<Connection> result = new ConnectionCallback(destination, callback);
selectorManager.connect(channel, result); selectorManager.connect(channel, result);
@ -596,6 +597,16 @@ public class HttpClient extends ContainerLifeCycle
this.dispatchIO = dispatchIO; this.dispatchIO = dispatchIO;
} }
public ProxyConfiguration getProxyConfiguration()
{
return proxyConfig;
}
public void setProxyConfiguration(ProxyConfiguration proxyConfig)
{
this.proxyConfig = proxyConfig;
}
@Override @Override
public void dump(Appendable out, String indent) throws IOException public void dump(Appendable out, String indent) throws IOException
{ {

View File

@ -155,6 +155,11 @@ public class HttpConnection extends AbstractConnection implements Connection
path = "/"; path = "/";
request.path(path); request.path(path);
} }
if (destination.isProxied() && HttpMethod.CONNECT != request.getMethod())
{
path = request.getURI();
request.path(path);
}
Fields fields = request.getParams(); Fields fields = request.getParams();
if (!fields.isEmpty()) if (!fields.isEmpty())
@ -359,13 +364,13 @@ public class HttpConnection extends AbstractConnection implements Connection
} }
} }
public boolean abort(HttpExchange exchange, String reason) public boolean abort(HttpExchange exchange, Throwable cause)
{ {
// We want the return value to be that of the response // We want the return value to be that of the response
// because if the response has already successfully // because if the response has already successfully
// arrived then we failed to abort the exchange // arrived then we failed to abort the exchange
sender.abort(exchange, reason); sender.abort(exchange, cause);
return receiver.abort(exchange, reason); return receiver.abort(exchange, cause);
} }
public void proceed(boolean proceed) public void proceed(boolean proceed)

View File

@ -77,9 +77,9 @@ public class HttpContentResponse implements ContentResponse
} }
@Override @Override
public boolean abort(String reason) public boolean abort(Throwable cause)
{ {
return response.abort(reason); return response.abort(cause);
} }
@Override @Override

View File

@ -98,10 +98,10 @@ public class HttpConversation implements Attributes
attributes.clear(); attributes.clear();
} }
public boolean abort(String reason) public boolean abort(Throwable cause)
{ {
HttpExchange exchange = exchanges.peekLast(); HttpExchange exchange = exchanges.peekLast();
return exchange != null && exchange.abort(reason); return exchange != null && exchange.abort(cause);
} }
@Override @Override

View File

@ -41,23 +41,27 @@ public class HttpCookieStore implements CookieStore
String host = destination.getHost(); String host = destination.getHost();
int port = destination.getPort(); int port = destination.getPort();
String key = host + ":" + port + path; String key = host + ":" + port;
// First lookup: direct hit // Root path lookup
Queue<HttpCookie> cookies = allCookies.get(key); Queue<HttpCookie> cookies = allCookies.get(key + "/");
if (cookies != null) if (cookies != null)
accumulateCookies(destination, cookies, result); accumulateCookies(destination, cookies, result);
// Second lookup: root path // Path lookup
if (!"/".equals(path)) String[] split = path.split("/");
for (int i = 1; i < split.length; i++)
{ {
key = host + ":" + port + "/"; String segment = split[i];
key += "/" + segment;
cookies = allCookies.get(key); cookies = allCookies.get(key);
if (cookies != null) if (cookies != null)
accumulateCookies(destination, cookies, result); accumulateCookies(destination, cookies, result);
if (segment.length() > 0)
key += "/";
} }
// Third lookup: parent domains // Domain lookup
int domains = host.split("\\.").length - 1; int domains = host.split("\\.").length - 1;
for (int i = 2; i <= domains; ++i) for (int i = 2; i <= domains; ++i)
{ {

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.client; package org.eclipse.jetty.client;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousCloseException; import java.nio.channels.AsynchronousCloseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -27,13 +28,19 @@ import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination; import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.ProxyConfiguration;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.TimedResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ -48,25 +55,28 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private final AtomicInteger connectionCount = new AtomicInteger(); private final AtomicInteger connectionCount = new AtomicInteger();
private final HttpClient client; private final HttpClient client;
private final String scheme; private final String scheme;
private final String host; private final InetSocketAddress address;
private final int port;
private final Queue<RequestContext> requests; private final Queue<RequestContext> requests;
private final BlockingQueue<Connection> idleConnections; private final BlockingQueue<Connection> idleConnections;
private final BlockingQueue<Connection> activeConnections; private final BlockingQueue<Connection> activeConnections;
private final RequestNotifier requestNotifier; private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier; private final ResponseNotifier responseNotifier;
private final InetSocketAddress proxyAddress;
public HttpDestination(HttpClient client, String scheme, String host, int port) public HttpDestination(HttpClient client, String scheme, String host, int port)
{ {
this.client = client; this.client = client;
this.scheme = scheme; this.scheme = scheme;
this.host = host; this.address = new InetSocketAddress(host, port);
this.port = port;
this.requests = new ArrayBlockingQueue<>(client.getMaxQueueSizePerAddress()); this.requests = new ArrayBlockingQueue<>(client.getMaxQueueSizePerAddress());
this.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress()); this.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress()); this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.requestNotifier = new RequestNotifier(client); this.requestNotifier = new RequestNotifier(client);
this.responseNotifier = new ResponseNotifier(client); this.responseNotifier = new ResponseNotifier(client);
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()) : null;
} }
protected BlockingQueue<Connection> getIdleConnections() protected BlockingQueue<Connection> getIdleConnections()
@ -88,23 +98,33 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
@Override @Override
public String getHost() public String getHost()
{ {
return host; return address.getHostString();
} }
@Override @Override
public int getPort() public int getPort()
{ {
return port; return address.getPort();
}
public InetSocketAddress getConnectAddress()
{
return isProxied() ? proxyAddress : address;
}
public boolean isProxied()
{
return proxyAddress != null;
} }
public void send(Request request, List<Response.ResponseListener> listeners) public void send(Request request, List<Response.ResponseListener> listeners)
{ {
if (!scheme.equals(request.getScheme())) if (!scheme.equals(request.getScheme()))
throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this); throw new IllegalArgumentException("Invalid request scheme " + request.getScheme() + " for destination " + this);
if (!host.equals(request.getHost())) if (!getHost().equals(request.getHost()))
throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this); throw new IllegalArgumentException("Invalid request host " + request.getHost() + " for destination " + this);
int port = request.getPort(); int port = request.getPort();
if (port >= 0 && this.port != port) if (port >= 0 && getPort() != port)
throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this); throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this);
RequestContext requestContext = new RequestContext(request, listeners); RequestContext requestContext = new RequestContext(request, listeners);
@ -139,7 +159,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
public Future<Connection> newConnection() public Future<Connection> newConnection()
{ {
FutureCallback<Connection> result = new FutureCallback<>(); FutureCallback<Connection> result = new FutureCallback<>();
newConnection(result); newConnection(new CONNECTCallback(result));
return result; return result;
} }
@ -170,30 +190,31 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
if (connectionCount.compareAndSet(current, next)) if (connectionCount.compareAndSet(current, next))
{ {
LOG.debug("Creating connection {}/{} for {}", next, maxConnections, this); LOG.debug("Creating connection {}/{} for {}", next, maxConnections, this);
newConnection(new Callback<Connection>()
CONNECTCallback connectCallback = new CONNECTCallback(new Callback<Connection>()
{ {
@Override @Override
public void completed(Connection connection) public void completed(Connection connection)
{ {
LOG.debug("Created connection {}/{} {} for {}", next, maxConnections, connection, HttpDestination.this);
process(connection, true); process(connection, true);
} }
@Override @Override
public void failed(Connection connection, final Throwable x) public void failed(final Connection connection, final Throwable x)
{ {
LOG.debug("Connection failed {} for {}", x, HttpDestination.this);
connectionCount.decrementAndGet();
client.getExecutor().execute(new Runnable() client.getExecutor().execute(new Runnable()
{ {
@Override @Override
public void run() public void run()
{ {
drain(x); drain(x);
if (connection != null)
connection.close();
} }
}); });
} }
}); });
newConnection(new TCPCallback(next, maxConnections, connectCallback));
// Try again the idle connections // Try again the idle connections
return idleConnections.poll(); return idleConnections.poll();
} }
@ -248,9 +269,10 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
{ {
final Request request = requestContext.request; final Request request = requestContext.request;
final List<Response.ResponseListener> listeners = requestContext.listeners; final List<Response.ResponseListener> listeners = requestContext.listeners;
if (request.isAborted()) Throwable cause = request.getAbortCause();
if (cause != null)
{ {
abort(request, listeners, "Aborted"); abort(request, listeners, cause);
LOG.debug("Aborted {} before processing", request); LOG.debug("Aborted {} before processing", request);
} }
else else
@ -300,10 +322,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
public void remove(Connection connection) public void remove(Connection connection)
{ {
LOG.debug("{} removed", connection); boolean removed = activeConnections.remove(connection);
connectionCount.decrementAndGet(); removed |= idleConnections.remove(connection);
activeConnections.remove(connection); if (removed)
idleConnections.remove(connection); {
LOG.debug("{} removed", connection);
connectionCount.decrementAndGet();
}
// We need to execute queued requests even if this connection failed. // We need to execute queued requests even if this connection failed.
// We may create a connection that is not needed, but it will eventually // We may create a connection that is not needed, but it will eventually
@ -334,7 +359,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
LOG.debug("Closed {}", this); LOG.debug("Closed {}", this);
} }
public boolean abort(Request request, String reason) public boolean abort(Request request, Throwable cause)
{ {
for (RequestContext requestContext : requests) for (RequestContext requestContext : requests)
{ {
@ -343,7 +368,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
if (requests.remove(requestContext)) if (requests.remove(requestContext))
{ {
// We were able to remove the pair, so it won't be processed // We were able to remove the pair, so it won't be processed
abort(request, requestContext.listeners, reason); abort(request, requestContext.listeners, cause);
LOG.debug("Aborted {} while queued", request); LOG.debug("Aborted {} while queued", request);
return true; return true;
} }
@ -352,13 +377,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return false; return false;
} }
private void abort(Request request, List<Response.ResponseListener> listeners, String reason) private void abort(Request request, List<Response.ResponseListener> listeners, Throwable cause)
{ {
HttpResponse response = new HttpResponse(request, listeners); HttpResponse response = new HttpResponse(request, listeners);
HttpResponseException responseFailure = new HttpResponseException(reason, response); responseNotifier.notifyFailure(listeners, response, cause);
responseNotifier.notifyFailure(listeners, response, responseFailure); responseNotifier.notifyComplete(listeners, new Result(request, cause, response, cause));
HttpRequestException requestFailure = new HttpRequestException(reason, request);
responseNotifier.notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
} }
@Override @Override
@ -382,7 +405,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s(%s://%s:%d)", HttpDestination.class.getSimpleName(), getScheme(), getHost(), getPort()); return String.format("%s(%s://%s:%d)%s",
HttpDestination.class.getSimpleName(),
getScheme(),
getHost(),
getPort(),
proxyAddress == null ? "" : " via " + proxyAddress.getHostString() + ":" + proxyAddress.getPort());
} }
private static class RequestContext private static class RequestContext
@ -396,4 +424,90 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
this.listeners = listeners; this.listeners = listeners;
} }
} }
private class TCPCallback implements Callback<Connection>
{
private final int current;
private final int max;
private final Callback<Connection> delegate;
private TCPCallback(int current, int max, Callback<Connection> delegate)
{
this.current = current;
this.max = max;
this.delegate = delegate;
}
@Override
public void completed(Connection connection)
{
LOG.debug("Created connection {}/{} {} for {}", current, max, connection, HttpDestination.this);
delegate.completed(connection);
}
@Override
public void failed(Connection connection, Throwable x)
{
LOG.debug("Connection failed {} for {}", x, HttpDestination.this);
connectionCount.decrementAndGet();
delegate.failed(connection, x);
}
}
private class CONNECTCallback implements Callback<Connection>
{
private final Callback<Connection> delegate;
private CONNECTCallback(Callback<Connection> delegate)
{
this.delegate = delegate;
}
@Override
public void completed(Connection connection)
{
boolean tunnel = isProxied() &&
"https".equalsIgnoreCase(getScheme()) &&
client.getSslContextFactory() != null;
if (tunnel)
tunnel(connection);
else
delegate.completed(connection);
}
@Override
public void failed(Connection connection, Throwable x)
{
delegate.failed(connection, x);
}
private void tunnel(final Connection connection)
{
String target = address.getHostString() + ":" + address.getPort();
Request connect = client.newRequest(proxyAddress.getHostString(), proxyAddress.getPort())
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.CONNECT)
.path(target)
.header(HttpHeader.HOST.asString(), target);
connection.send(connect, new TimedResponseListener(client.getConnectTimeout(), TimeUnit.MILLISECONDS, connect, new Response.CompleteListener()
{
@Override
public void onComplete(Result result)
{
if (result.isFailed())
{
failed(connection, result.getFailure());
}
else
{
Response response = result.getResponse();
if (response.getStatus() == 200)
delegate.completed(connection);
else
failed(connection, new HttpResponseException("Received " + response + " for " + result.getRequest(), response));
}
}
}));
}
}
} }

View File

@ -193,10 +193,10 @@ public class HttpExchange
return new AtomicMarkableReference<>(result, modified); return new AtomicMarkableReference<>(result, modified);
} }
public boolean abort(String reason) public boolean abort(Throwable cause)
{ {
LOG.debug("Aborting {} reason {}", this, reason); LOG.debug("Aborting {} reason {}", this, cause);
boolean aborted = connection.abort(this, reason); boolean aborted = connection.abort(this, cause);
LOG.debug("Aborted {}: {}", this, aborted); LOG.debug("Aborted {}: {}", this, aborted);
return aborted; return aborted;
} }

View File

@ -387,9 +387,9 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
fail(new TimeoutException()); fail(new TimeoutException());
} }
public boolean abort(HttpExchange exchange, String reason) public boolean abort(HttpExchange exchange, Throwable cause)
{ {
return fail(new HttpResponseException(reason == null ? "Response aborted" : reason, exchange.getResponse())); return fail(cause);
} }
private boolean updateState(State from, State to) private boolean updateState(State from, State to)

View File

@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -63,7 +64,7 @@ public class HttpRequest implements Request
private long idleTimeout; private long idleTimeout;
private ContentProvider content; private ContentProvider content;
private boolean followRedirects; private boolean followRedirects;
private volatile boolean aborted; private volatile Throwable aborted;
public HttpRequest(HttpClient client, URI uri) public HttpRequest(HttpClient client, URI uri)
{ {
@ -403,17 +404,17 @@ public class HttpRequest implements Request
} }
@Override @Override
public boolean abort(String reason) public boolean abort(Throwable cause)
{ {
aborted = true; aborted = Objects.requireNonNull(cause);
if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, reason)) if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, cause))
return true; return true;
HttpConversation conversation = client.getConversation(getConversationID(), false); HttpConversation conversation = client.getConversation(getConversationID(), false);
return conversation != null && conversation.abort(reason); return conversation != null && conversation.abort(cause);
} }
@Override @Override
public boolean isAborted() public Throwable getAbortCause()
{ {
return aborted; return aborted;
} }

View File

@ -98,9 +98,9 @@ public class HttpResponse implements Response
} }
@Override @Override
public boolean abort(String reason) public boolean abort(Throwable cause)
{ {
return request.abort(reason); return request.abort(cause);
} }
@Override @Override

View File

@ -82,9 +82,10 @@ public class HttpSender
} }
Request request = exchange.getRequest(); Request request = exchange.getRequest();
if (request.isAborted()) Throwable cause = request.getAbortCause();
if (cause != null)
{ {
exchange.abort(null); exchange.abort(cause);
} }
else else
{ {
@ -400,7 +401,7 @@ public class HttpSender
Result result = completion.getReference(); Result result = completion.getReference();
boolean notCommitted = current == State.IDLE || current == State.SEND; boolean notCommitted = current == State.IDLE || current == State.SEND;
if (result == null && notCommitted && !request.isAborted()) if (result == null && notCommitted && request.getAbortCause() == null)
{ {
result = exchange.responseComplete(failure).getReference(); result = exchange.responseComplete(failure).getReference();
exchange.terminateResponse(); exchange.terminateResponse();
@ -418,12 +419,12 @@ public class HttpSender
return true; return true;
} }
public boolean abort(HttpExchange exchange, String reason) public boolean abort(HttpExchange exchange, Throwable cause)
{ {
State current = state.get(); State current = state.get();
boolean abortable = current == State.IDLE || current == State.SEND || boolean abortable = current == State.IDLE || current == State.SEND ||
current == State.COMMIT && contentIterator.hasNext(); current == State.COMMIT && contentIterator.hasNext();
return abortable && fail(new HttpRequestException(reason == null ? "Request aborted" : reason, exchange.getRequest())); return abortable && fail(cause);
} }
private void releaseBuffers(ByteBufferPool bufferPool, ByteBuffer header, ByteBuffer chunk) private void releaseBuffers(ByteBufferPool bufferPool, ByteBuffer header, ByteBuffer chunk)

View File

@ -126,8 +126,9 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
@Override @Override
public void onBegin(Request redirect) public void onBegin(Request redirect)
{ {
if (request.isAborted()) Throwable cause = request.getAbortCause();
redirect.abort(null); if (cause != null)
redirect.abort(cause);
} }
}); });

View File

@ -16,36 +16,41 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.server.handler; package org.eclipse.jetty.client.api;
import org.eclipse.jetty.server.Handler; import java.util.HashSet;
import java.util.Set;
public class ProxyConfiguration
/* ------------------------------------------------------------ */
/** ProxyHandler.
* <p>This class has been renamed to ConnectHandler, as it only implements
* the CONNECT method (and a ProxyServlet must be used for full proxy handling).
* @deprecated Use {@link ConnectHandler}
*/
public class ProxyHandler extends ConnectHandler
{ {
public ProxyHandler() private final Set<String> excluded = new HashSet<>();
private final String host;
private final int port;
public ProxyConfiguration(String host, int port)
{ {
super(); this.host = host;
this.port = port;
} }
public ProxyHandler(Handler handler, String[] white, String[] black) public String getHost()
{ {
super(handler,white,black); return host;
} }
public ProxyHandler(Handler handler) public int getPort()
{ {
super(handler); return port;
} }
public ProxyHandler(String[] white, String[] black) public boolean matches(String host, int port)
{ {
super(white,black); String hostPort = host + ":" + port;
return !getExcludedHosts().contains(hostPort);
}
public Set<String> getExcludedHosts()
{
return excluded;
} }
} }

View File

@ -308,15 +308,16 @@ public interface Request
/** /**
* Attempts to abort the send of this request. * Attempts to abort the send of this request.
* *
* @param reason the abort reason * @param cause the abort cause, must not be null
* @return whether the abort succeeded * @return whether the abort succeeded
*/ */
boolean abort(String reason); boolean abort(Throwable cause);
/** /**
* @return whether {@link #abort(String)} was called * @return the abort cause passed to {@link #abort(Throwable)},
* or null if this request has not been aborted
*/ */
boolean isAborted(); Throwable getAbortCause();
public interface RequestListener extends EventListener public interface RequestListener extends EventListener
{ {

View File

@ -71,10 +71,10 @@ public interface Response
/** /**
* Attempts to abort the receive of this response. * Attempts to abort the receive of this response.
* *
* @param reason the abort reason * @param cause the abort cause, must not be null
* @return whether the abort succeeded * @return whether the abort succeeded
*/ */
boolean abort(String reason); boolean abort(Throwable cause);
public interface ResponseListener extends EventListener public interface ResponseListener extends EventListener
{ {

View File

@ -18,9 +18,6 @@
package org.eclipse.jetty.client.util; package org.eclipse.jetty.client.util;
import java.io.UnsupportedEncodingException;
import java.nio.charset.UnsupportedCharsetException;
import org.eclipse.jetty.client.api.Authentication; import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
@ -60,15 +57,8 @@ public class BasicAuthentication implements Authentication
public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context) public Result authenticate(Request request, ContentResponse response, String wwwAuthenticate, Attributes context)
{ {
String encoding = StringUtil.__ISO_8859_1; String encoding = StringUtil.__ISO_8859_1;
try String value = "Basic " + B64Code.encode(user + ":" + password, encoding);
{ return new BasicResult(request.getURI(), value);
String value = "Basic " + B64Code.encode(user + ":" + password, encoding);
return new BasicResult(request.getURI(), value);
}
catch (UnsupportedEncodingException x)
{
throw new UnsupportedCharsetException(encoding);
}
} }
private static class BasicResult implements Result private static class BasicResult implements Result

View File

@ -55,7 +55,7 @@ public class BlockingResponseListener extends BufferingResponseListener implemen
public boolean cancel(boolean mayInterruptIfRunning) public boolean cancel(boolean mayInterruptIfRunning)
{ {
cancelled = true; cancelled = true;
return request.abort("Cancelled"); return request.abort(new CancellationException());
} }
@Override @Override
@ -83,8 +83,9 @@ public class BlockingResponseListener extends BufferingResponseListener implemen
boolean expired = !latch.await(timeout, unit); boolean expired = !latch.await(timeout, unit);
if (expired) if (expired)
{ {
request.abort("Total timeout elapsed"); TimeoutException reason = new TimeoutException();
throw new TimeoutException(); request.abort(reason);
throw reason;
} }
return getResult(); return getResult();
} }

View File

@ -64,7 +64,7 @@ public abstract class BufferingResponseListener extends Response.Listener.Empty
HttpFields headers = response.getHeaders(); HttpFields headers = response.getHeaders();
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString()); long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
if (length > maxLength) if (length > maxLength)
response.abort("Buffering capacity exceeded"); response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
String contentType = headers.get(HttpHeader.CONTENT_TYPE); String contentType = headers.get(HttpHeader.CONTENT_TYPE);
if (contentType != null) if (contentType != null)

View File

@ -49,6 +49,13 @@ public class InputStreamContentProvider implements ContentProvider
return -1; return -1;
} }
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
{
if (length <= 0)
return BufferUtil.EMPTY_BUFFER;
return ByteBuffer.wrap(buffer, offset, length);
}
@Override @Override
public Iterator<ByteBuffer> iterator() public Iterator<ByteBuffer> iterator()
{ {
@ -71,18 +78,18 @@ public class InputStreamContentProvider implements ContentProvider
int read = stream.read(buffer); int read = stream.read(buffer);
if (read > 0) if (read > 0)
{ {
return ByteBuffer.wrap(buffer, 0, read); return onRead(buffer, 0, read);
} }
else if (read < 0) else if (read < 0)
{ {
if (eof) if (eof)
throw new NoSuchElementException(); throw new NoSuchElementException();
eof = true; eof = true;
return BufferUtil.EMPTY_BUFFER; return onRead(buffer, 0, -1);
} }
else else
{ {
return BufferUtil.EMPTY_BUFFER; return onRead(buffer, 0, 0);
} }
} }
catch (IOException x) catch (IOException x)

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.Schedulable; import org.eclipse.jetty.client.Schedulable;
@ -38,14 +39,14 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
private final long timeout; private final long timeout;
private final TimeUnit unit; private final TimeUnit unit;
private final Request request; private final Request request;
private final Response.Listener delegate; private final Response.CompleteListener delegate;
public TimedResponseListener(long timeout, TimeUnit unit, Request request) public TimedResponseListener(long timeout, TimeUnit unit, Request request)
{ {
this(timeout, unit, request, new Empty()); this(timeout, unit, request, new Empty());
} }
public TimedResponseListener(long timeout, TimeUnit unit, Request request, Response.Listener delegate) public TimedResponseListener(long timeout, TimeUnit unit, Request request, Response.CompleteListener delegate)
{ {
this.timeout = timeout; this.timeout = timeout;
this.unit = unit; this.unit = unit;
@ -56,31 +57,36 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
@Override @Override
public void onBegin(Response response) public void onBegin(Response response)
{ {
delegate.onBegin(response); if (delegate instanceof Response.BeginListener)
((Response.BeginListener)delegate).onBegin(response);
} }
@Override @Override
public void onHeaders(Response response) public void onHeaders(Response response)
{ {
delegate.onHeaders(response); if (delegate instanceof Response.HeadersListener)
((Response.HeadersListener)delegate).onHeaders(response);
} }
@Override @Override
public void onContent(Response response, ByteBuffer content) public void onContent(Response response, ByteBuffer content)
{ {
delegate.onContent(response, content); if (delegate instanceof Response.ContentListener)
((Response.ContentListener)delegate).onContent(response, content);
} }
@Override @Override
public void onSuccess(Response response) public void onSuccess(Response response)
{ {
delegate.onSuccess(response); if (delegate instanceof Response.SuccessListener)
((Response.SuccessListener)delegate).onSuccess(response);
} }
@Override @Override
public void onFailure(Response response, Throwable failure) public void onFailure(Response response, Throwable failure)
{ {
delegate.onFailure(response, failure); if (delegate instanceof Response.FailureListener)
((Response.FailureListener)delegate).onFailure(response, failure);
} }
@Override @Override
@ -111,7 +117,7 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
@Override @Override
public void run() public void run()
{ {
request.abort("Total timeout elapsed"); request.abort(new TimeoutException("Total timeout elapsed"));
} }
public boolean cancel() public boolean cancel()

View File

@ -408,7 +408,7 @@ public class HttpClientContinueTest extends AbstractHttpClientServerTest
@Override @Override
public void onBegin(Response response) public void onBegin(Response response)
{ {
response.abort(null); response.abort(new Exception());
} }
@Override @Override

View File

@ -151,7 +151,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
TimeUnit.MILLISECONDS.sleep(2 * timeout); TimeUnit.MILLISECONDS.sleep(2 * timeout);
Assert.assertFalse(request.isAborted()); Assert.assertNull(request.getAbortCause());
} }
@Slow @Slow
@ -208,7 +208,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
TimeUnit.MILLISECONDS.sleep(2 * timeout); TimeUnit.MILLISECONDS.sleep(2 * timeout);
Assert.assertFalse(request.isAborted()); Assert.assertNull(request.getAbortCause());
} }
} }

View File

@ -100,6 +100,21 @@ public class HttpCookieStoreTest
Assert.assertEquals("1", cookie.getValue()); Assert.assertEquals("1", cookie.getValue());
} }
@Test
public void testCookieStoredWithPathIsRetrievedWithChildPath() throws Exception
{
CookieStore cookies = new HttpCookieStore();
Destination destination = new HttpDestination(client, "http", "localhost", 80);
Assert.assertTrue(cookies.addCookie(destination, new HttpCookie("a", "1", null, "/path")));
List<HttpCookie> result = cookies.findCookies(destination, "/path/child");
Assert.assertNotNull(result);
Assert.assertEquals(1, result.size());
HttpCookie cookie = result.get(0);
Assert.assertEquals("a", cookie.getName());
Assert.assertEquals("1", cookie.getValue());
}
@Test @Test
public void testCookieStoredWithParentDomainIsRetrievedWithChildDomain() throws Exception public void testCookieStoredWithParentDomainIsRetrievedWithChildDomain() throws Exception
{ {
@ -118,6 +133,19 @@ public class HttpCookieStoreTest
Assert.assertEquals(2, result.size()); Assert.assertEquals(2, result.size());
} }
@Test
public void testCookieStoredWithChildDomainIsNotRetrievedWithParentDomain() throws Exception
{
CookieStore cookies = new HttpCookieStore();
Destination childDestination = new HttpDestination(client, "http", "child.localhost.org", 80);
Assert.assertTrue(cookies.addCookie(childDestination, new HttpCookie("b", "2", null, "/")));
Destination parentDestination = new HttpDestination(client, "http", "localhost.org", 80);
List<HttpCookie> result = cookies.findCookies(parentDestination, "/path");
Assert.assertNotNull(result);
Assert.assertEquals(0, result.size());
}
@Test @Test
public void testExpiredCookieIsNotRetrieved() throws Exception public void testExpiredCookieIsNotRetrieved() throws Exception
{ {

View File

@ -33,7 +33,6 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannel;
@ -42,7 +41,6 @@ import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -58,6 +56,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{ {
start(new EmptyServerHandler()); start(new EmptyServerHandler());
final Throwable cause = new Exception();
final AtomicBoolean begin = new AtomicBoolean(); final AtomicBoolean begin = new AtomicBoolean();
try try
{ {
@ -68,7 +67,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onQueued(Request request) public void onQueued(Request request)
{ {
request.abort(null); request.abort(cause);
} }
@Override @Override
@ -82,7 +81,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
catch (ExecutionException x) catch (ExecutionException x)
{ {
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class)); Assert.assertSame(cause, x.getCause());
Assert.assertFalse(begin.get()); Assert.assertFalse(begin.get());
} }
} }
@ -93,6 +92,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{ {
start(new EmptyServerHandler()); start(new EmptyServerHandler());
final Throwable cause = new Exception();
final CountDownLatch aborted = new CountDownLatch(1); final CountDownLatch aborted = new CountDownLatch(1);
final CountDownLatch headers = new CountDownLatch(1); final CountDownLatch headers = new CountDownLatch(1);
try try
@ -104,7 +104,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onBegin(Request request) public void onBegin(Request request)
{ {
if (request.abort(null)) if (request.abort(cause))
aborted.countDown(); aborted.countDown();
} }
@ -119,7 +119,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
catch (ExecutionException x) catch (ExecutionException x)
{ {
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class)); Assert.assertSame(cause, x.getCause());
Assert.assertTrue(aborted.await(5, TimeUnit.SECONDS)); Assert.assertTrue(aborted.await(5, TimeUnit.SECONDS));
Assert.assertFalse(headers.await(1, TimeUnit.SECONDS)); Assert.assertFalse(headers.await(1, TimeUnit.SECONDS));
} }
@ -134,6 +134,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
// A) the request is failed before the response arrived, then we get an ExecutionException // A) the request is failed before the response arrived, then we get an ExecutionException
// B) the request is failed after the response arrived, we get the 200 OK // B) the request is failed after the response arrived, we get the 200 OK
final Throwable cause = new Exception();
final CountDownLatch aborted = new CountDownLatch(1); final CountDownLatch aborted = new CountDownLatch(1);
try try
{ {
@ -144,7 +145,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onHeaders(Request request) public void onHeaders(Request request)
{ {
if (request.abort(null)) if (request.abort(cause))
aborted.countDown(); aborted.countDown();
} }
}) })
@ -154,7 +155,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
catch (ExecutionException x) catch (ExecutionException x)
{ {
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class)); Assert.assertSame(cause, x.getCause());
Assert.assertTrue(aborted.await(5, TimeUnit.SECONDS)); Assert.assertTrue(aborted.await(5, TimeUnit.SECONDS));
} }
} }
@ -181,12 +182,8 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
}); });
// Test can behave in 3 ways:
// A) non-SSL, if the request is failed before the response arrived, then we get an ExecutionException
// B) non-SSL, if the request is failed after the response arrived, then we get the 500
// C) SSL, the server tries to write the 500, but the connection is already closed, the client
// reads -1 with a pending exchange and fails the response with an EOFException
StdErrLog.getLogger(HttpChannel.class).setHideStacks(true); StdErrLog.getLogger(HttpChannel.class).setHideStacks(true);
final Throwable cause = new Exception();
try try
{ {
client.newRequest("localhost", connector.getLocalPort()) client.newRequest("localhost", connector.getLocalPort())
@ -196,7 +193,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onHeaders(Request request) public void onHeaders(Request request)
{ {
request.abort(null); request.abort(cause);
} }
}) })
.content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1})) .content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
@ -212,24 +209,15 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
catch (ExecutionException x) catch (ExecutionException x)
{ {
Throwable cause = x.getCause(); Throwable abort = x.getCause();
if (cause instanceof EOFException) if (abort instanceof EOFException)
{ {
// Server closed abruptly, behavior C // Server closed abruptly
System.err.println("C");
} }
else if (cause instanceof HttpRequestException) else if (abort == cause)
{ {
// Request failed, behavior B // Expected
HttpRequestException xx = (HttpRequestException)cause;
Request request = xx.getRequest();
Assert.assertNotNull(request);
}
else if (cause instanceof HttpResponseException)
{
// Response failed, behavior A
HttpResponseException xx = (HttpResponseException)cause;
Response response = xx.getResponse();
Assert.assertNotNull(response);
} }
else else
{ {
@ -270,7 +258,8 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
TimeUnit.MILLISECONDS.sleep(delay); TimeUnit.MILLISECONDS.sleep(delay);
request.abort(null); Throwable cause = new Exception();
request.abort(cause);
try try
{ {
@ -278,7 +267,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
catch (ExecutionException x) catch (ExecutionException x)
{ {
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class)); Assert.assertSame(cause, x.getCause());
} }
} }
@ -296,6 +285,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
}); });
final Throwable cause = new Exception();
client.getProtocolHandlers().clear(); client.getProtocolHandlers().clear();
client.getProtocolHandlers().add(new RedirectProtocolHandler(client) client.getProtocolHandlers().add(new RedirectProtocolHandler(client)
{ {
@ -304,7 +294,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{ {
// Abort the request after the 3xx response but before issuing the next request // Abort the request after the 3xx response but before issuing the next request
if (!result.isFailed()) if (!result.isFailed())
result.getRequest().abort(null); result.getRequest().abort(cause);
super.onComplete(result); super.onComplete(result);
} }
}); });
@ -320,7 +310,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
} }
catch (ExecutionException x) catch (ExecutionException x)
{ {
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class)); Assert.assertSame(cause, x.getCause());
} }
} }
} }

View File

@ -60,7 +60,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onBegin(Response response) public void onBegin(Response response)
{ {
response.abort(null); response.abort(new Exception());
} }
}) })
.send(new Response.CompleteListener() .send(new Response.CompleteListener()
@ -88,7 +88,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onHeaders(Response response) public void onHeaders(Response response)
{ {
response.abort(null); response.abort(new Exception());
} }
}) })
.send(new Response.CompleteListener() .send(new Response.CompleteListener()
@ -136,7 +136,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
@Override @Override
public void onContent(Response response, ByteBuffer content) public void onContent(Response response, ByteBuffer content)
{ {
response.abort(null); response.abort(new Exception());
} }
}) })
.send(new Response.CompleteListener() .send(new Response.CompleteListener()

View File

@ -191,7 +191,7 @@ public class Usage
} }
else else
{ {
response.abort(null); response.abort(new Exception());
} }
} }

View File

@ -1,88 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<parent> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<artifactId>jetty-project</artifactId> <parent>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<version>9.0.0-SNAPSHOT</version> <artifactId>jetty-project</artifactId>
</parent> <version>9.0.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion> </parent>
<artifactId>jetty-proxy</artifactId>
<name>Jetty :: Proxy</name> <modelVersion>4.0.0</modelVersion>
<description>Jetty Proxy</description> <artifactId>jetty-proxy</artifactId>
<properties> <name>Jetty :: Proxy</name>
<bundle-symbolic-name>${project.groupId}.servlets</bundle-symbolic-name> <description>Jetty Proxy</description>
</properties>
<build> <properties>
<plugins> <bundle-symbolic-name>${project.groupId}.proxy</bundle-symbolic-name>
<plugin> </properties>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId> <build>
<extensions>true</extensions> <plugins>
<executions> <plugin>
<execution> <groupId>org.apache.felix</groupId>
<goals> <artifactId>maven-bundle-plugin</artifactId>
<goal>manifest</goal> <extensions>true</extensions>
</goals> <executions>
<configuration> <execution>
<instructions> <goals>
<Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package> <goal>manifest</goal>
</instructions> </goals>
</configuration> <configuration>
</execution> <instructions>
</executions> <Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
</plugin> </instructions>
<plugin> </configuration>
<!-- </execution>
Required for OSGI </executions>
--> </plugin>
<groupId>org.apache.maven.plugins</groupId> <plugin>
<artifactId>maven-jar-plugin</artifactId> <!--
<configuration> Required for OSGI
<archive> -->
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile> <groupId>org.apache.maven.plugins</groupId>
</archive> <artifactId>maven-jar-plugin</artifactId>
</configuration> <configuration>
</plugin> <archive>
<plugin> <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
<groupId>org.codehaus.mojo</groupId> </archive>
<artifactId>findbugs-maven-plugin</artifactId> </configuration>
<configuration> </plugin>
<onlyAnalyze>org.eclipse.jetty.servlets.*</onlyAnalyze> <plugin>
</configuration> <groupId>org.codehaus.mojo</groupId>
</plugin> <artifactId>findbugs-maven-plugin</artifactId>
</plugins> <configuration>
</build> <onlyAnalyze>org.eclipse.jetty.proxy.*</onlyAnalyze>
<dependencies> </configuration>
<dependency> </plugin>
<groupId>org.eclipse.jetty.toolchain</groupId> </plugins>
<artifactId>jetty-test-helper</artifactId> </build>
<scope>test</scope>
</dependency> <dependencies>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-continuation</artifactId> <artifactId>jetty-client</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-util</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<scope>provided</scope> </dependency>
</dependency> <dependency>
<dependency> <groupId>org.eclipse.jetty</groupId>
<groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId>
<artifactId>jetty-client</artifactId> <version>${project.version}</version>
<version>${project.version}</version> </dependency>
</dependency> <dependency>
<dependency> <groupId>org.eclipse.jetty.orbit</groupId>
<groupId>org.eclipse.jetty</groupId> <artifactId>javax.servlet</artifactId>
<artifactId>jetty-util</artifactId> <scope>provided</scope>
<version>${project.version}</version> </dependency>
</dependency> <dependency>
<dependency> <groupId>org.eclipse.jetty</groupId>
<groupId>org.eclipse.jetty.orbit</groupId> <artifactId>jetty-webapp</artifactId>
<artifactId>javax.servlet</artifactId> <version>${project.version}</version>
<scope>provided</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project> </project>

View File

@ -18,48 +18,255 @@
package org.eclipse.jetty.proxy; package org.eclipse.jetty.proxy;
import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.UnavailableException; import javax.servlet.UnavailableException;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Request;
/**
* 6
*/
public class BalancerServlet extends ProxyServlet public class BalancerServlet extends ProxyServlet
{ {
private static final String BALANCER_MEMBER_PREFIX = "balancerMember.";
private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
private static final class BalancerMember static
{ {
List<String> params = new LinkedList<>();
params.add("hostHeader");
params.add("whiteList");
params.add("blackList");
FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
}
private String _name; private static final List<String> REVERSE_PROXY_HEADERS;
private String _proxyTo; static
{
List<String> params = new LinkedList<>();
params.add("Location");
params.add("Content-Location");
params.add("URI");
REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
}
private HttpURI _backendURI; private static final String JSESSIONID = "jsessionid";
private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
private final List<BalancerMember> _balancerMembers = new ArrayList<>();
private final AtomicLong counter = new AtomicLong();
private boolean _stickySessions;
private boolean _proxyPassReverse;
@Override
public void init() throws ServletException
{
validateConfig();
super.init();
initStickySessions();
initBalancers();
initProxyPassReverse();
}
private void validateConfig() throws ServletException
{
for (String initParameterName : Collections.list(getServletConfig().getInitParameterNames()))
{
if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
{
throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
}
}
}
private void initStickySessions() throws ServletException
{
_stickySessions = Boolean.parseBoolean(getServletConfig().getInitParameter("stickySessions"));
}
private void initBalancers() throws ServletException
{
Set<BalancerMember> members = new HashSet<>();
for (String balancerName : getBalancerNames())
{
String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".proxyTo";
String proxyTo = getServletConfig().getInitParameter(memberProxyToParam);
if (proxyTo == null || proxyTo.trim().length() == 0)
throw new UnavailableException(memberProxyToParam + " parameter is empty.");
members.add(new BalancerMember(balancerName, proxyTo));
}
_balancerMembers.addAll(members);
}
private void initProxyPassReverse()
{
_proxyPassReverse = Boolean.parseBoolean(getServletConfig().getInitParameter("proxyPassReverse"));
}
private Set<String> getBalancerNames() throws ServletException
{
Set<String> names = new HashSet<>();
for (String initParameterName : Collections.list(getServletConfig().getInitParameterNames()))
{
if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
continue;
int endOfNameIndex = initParameterName.lastIndexOf(".");
if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(), endOfNameIndex));
}
return names;
}
@Override
protected URI rewriteURI(HttpServletRequest request)
{
BalancerMember balancerMember = selectBalancerMember(request);
_log.debug("Selected {}", balancerMember);
String path = request.getRequestURI();
String query = request.getQueryString();
if (query != null)
path += "?" + query;
return URI.create(balancerMember.getProxyTo() + "/" + path).normalize();
}
private BalancerMember selectBalancerMember(HttpServletRequest request)
{
if (_stickySessions)
{
String name = getBalancerMemberNameFromSessionId(request);
if (name != null)
{
BalancerMember balancerMember = findBalancerMemberByName(name);
if (balancerMember != null)
return balancerMember;
}
}
int index = (int)(counter.getAndIncrement() % _balancerMembers.size());
return _balancerMembers.get(index);
}
private BalancerMember findBalancerMemberByName(String name)
{
for (BalancerMember balancerMember : _balancerMembers)
{
if (balancerMember.getName().equals(name))
return balancerMember;
}
return null;
}
private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
{
String name = getBalancerMemberNameFromSessionCookie(request);
if (name == null)
name = getBalancerMemberNameFromURL(request);
return name;
}
private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
{
for (Cookie cookie : request.getCookies())
{
if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
return extractBalancerMemberNameFromSessionId(cookie.getValue());
}
return null;
}
private String getBalancerMemberNameFromURL(HttpServletRequest request)
{
String requestURI = request.getRequestURI();
int idx = requestURI.lastIndexOf(";");
if (idx > 0)
{
String requestURISuffix = requestURI.substring(idx + 1);
if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
return extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
}
return null;
}
private String extractBalancerMemberNameFromSessionId(String sessionId)
{
int idx = sessionId.lastIndexOf(".");
if (idx > 0)
{
String sessionIdSuffix = sessionId.substring(idx + 1);
return sessionIdSuffix.length() > 0 ? sessionIdSuffix : null;
}
return null;
}
@Override
protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
{
if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
{
URI locationURI = URI.create(headerValue).normalize();
if (locationURI.isAbsolute() && isBackendLocation(locationURI))
{
String newURI = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
String component = locationURI.getRawPath();
if (component != null)
newURI += component;
component = locationURI.getRawQuery();
if (component != null)
newURI += "?" + component;
component = locationURI.getRawFragment();
if (component != null)
newURI += "#" + component;
return URI.create(newURI).normalize().toString();
}
}
return headerValue;
}
private boolean isBackendLocation(URI locationURI)
{
for (BalancerMember balancerMember : _balancerMembers)
{
URI backendURI = balancerMember.getBackendURI();
if (backendURI.getHost().equals(locationURI.getHost()) &&
backendURI.getScheme().equals(locationURI.getScheme())
&& backendURI.getPort() == locationURI.getPort())
{
return true;
}
}
return false;
}
@Override
public boolean validateDestination(String host, int port)
{
return true;
}
private static class BalancerMember
{
private final String _name;
private final String _proxyTo;
private final URI _backendURI;
public BalancerMember(String name, String proxyTo) public BalancerMember(String name, String proxyTo)
{ {
super();
_name = name; _name = name;
_proxyTo = proxyTo; _proxyTo = proxyTo;
_backendURI = new HttpURI(_proxyTo); _backendURI = URI.create(_proxyTo).normalize();
}
public String getName()
{
return _name;
} }
public String getProxyTo() public String getProxyTo()
@ -67,7 +274,7 @@ public class BalancerServlet extends ProxyServlet
return _proxyTo; return _proxyTo;
} }
public HttpURI getBackendURI() public URI getBackendURI()
{ {
return _backendURI; return _backendURI;
} }
@ -75,16 +282,13 @@ public class BalancerServlet extends ProxyServlet
@Override @Override
public String toString() public String toString()
{ {
return "BalancerMember [_name=" + _name + ", _proxyTo=" + _proxyTo + "]"; return String.format("%s[name=%s,proxyTo=%s]", getClass().getSimpleName(), _name, _proxyTo);
} }
@Override @Override
public int hashCode() public int hashCode()
{ {
final int prime = 31; return _name.hashCode();
int result = 1;
result = prime * result + ((_name == null)?0:_name.hashCode());
return result;
} }
@Override @Override
@ -96,327 +300,8 @@ public class BalancerServlet extends ProxyServlet
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
BalancerMember other = (BalancerMember)obj; BalancerMember that = (BalancerMember)obj;
if (_name == null) return _name.equals(that._name);
{
if (other._name != null)
return false;
}
else if (!_name.equals(other._name))
return false;
return true;
}
}
private static final class RoundRobinIterator implements Iterator<BalancerMember>
{
private BalancerMember[] _balancerMembers;
private AtomicInteger _index;
public RoundRobinIterator(Collection<BalancerMember> balancerMembers)
{
_balancerMembers = (BalancerMember[])balancerMembers.toArray(new BalancerMember[balancerMembers.size()]);
_index = new AtomicInteger(-1);
}
public boolean hasNext()
{
return true;
}
public BalancerMember next()
{
BalancerMember balancerMember = null;
while (balancerMember == null)
{
int currentIndex = _index.get();
int nextIndex = (currentIndex + 1) % _balancerMembers.length;
if (_index.compareAndSet(currentIndex,nextIndex))
{
balancerMember = _balancerMembers[nextIndex];
}
}
return balancerMember;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
private static final String BALANCER_MEMBER_PREFIX = "BalancerMember.";
private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
static
{
List<String> params = new LinkedList<String>();
params.add("HostHeader");
params.add("whiteList");
params.add("blackList");
FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
}
private static final List<String> REVERSE_PROXY_HEADERS;
static
{
List<String> params = new LinkedList<String>();
params.add("Location");
params.add("Content-Location");
params.add("URI");
REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
}
private static final String JSESSIONID = "jsessionid";
private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
private boolean _stickySessions;
private Set<BalancerMember> _balancerMembers = new HashSet<BalancerMember>();
private boolean _proxyPassReverse;
private RoundRobinIterator _roundRobinIterator;
@Override
public void init(ServletConfig config) throws ServletException
{
validateConfig(config);
super.init(config);
initStickySessions(config);
initBalancers(config);
initProxyPassReverse(config);
postInit();
}
private void validateConfig(ServletConfig config) throws ServletException
{
@SuppressWarnings("unchecked")
List<String> initParameterNames = Collections.list(config.getInitParameterNames());
for (String initParameterName : initParameterNames)
{
if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
{
throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
}
} }
} }
private void initStickySessions(ServletConfig config) throws ServletException
{
_stickySessions = "true".equalsIgnoreCase(config.getInitParameter("StickySessions"));
}
private void initBalancers(ServletConfig config) throws ServletException
{
Set<String> balancerNames = getBalancerNames(config);
for (String balancerName : balancerNames)
{
String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".ProxyTo";
String proxyTo = config.getInitParameter(memberProxyToParam);
if (proxyTo == null || proxyTo.trim().length() == 0)
{
throw new UnavailableException(memberProxyToParam + " parameter is empty.");
}
_balancerMembers.add(new BalancerMember(balancerName,proxyTo));
}
}
private void initProxyPassReverse(ServletConfig config)
{
_proxyPassReverse = "true".equalsIgnoreCase(config.getInitParameter("ProxyPassReverse"));
}
private void postInit()
{
_roundRobinIterator = new RoundRobinIterator(_balancerMembers);
}
private Set<String> getBalancerNames(ServletConfig config) throws ServletException
{
Set<String> names = new HashSet<String>();
@SuppressWarnings("unchecked")
List<String> initParameterNames = Collections.list(config.getInitParameterNames());
for (String initParameterName : initParameterNames)
{
if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
{
continue;
}
int endOfNameIndex = initParameterName.lastIndexOf(".");
if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
{
throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
}
names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(),endOfNameIndex));
}
return names;
}
@Override
protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
{
BalancerMember balancerMember = selectBalancerMember(request);
try
{
URI dstUri = new URI(balancerMember.getProxyTo() + "/" + uri).normalize();
return new HttpURI(dstUri.toString());
}
catch (URISyntaxException e)
{
throw new MalformedURLException(e.getMessage());
}
}
private BalancerMember selectBalancerMember(HttpServletRequest request)
{
BalancerMember balancerMember = null;
if (_stickySessions)
{
String name = getBalancerMemberNameFromSessionId(request);
if (name != null)
{
balancerMember = findBalancerMemberByName(name);
if (balancerMember != null)
{
return balancerMember;
}
}
}
return _roundRobinIterator.next();
}
private BalancerMember findBalancerMemberByName(String name)
{
BalancerMember example = new BalancerMember(name,"");
for (BalancerMember balancerMember : _balancerMembers)
{
if (balancerMember.equals(example))
{
return balancerMember;
}
}
return null;
}
private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
{
String name = getBalancerMemberNameFromSessionCookie(request);
if (name == null)
{
name = getBalancerMemberNameFromURL(request);
}
return name;
}
private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
{
Cookie[] cookies = request.getCookies();
String name = null;
for (Cookie cookie : cookies)
{
if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
{
name = extractBalancerMemberNameFromSessionId(cookie.getValue());
break;
}
}
return name;
}
private String getBalancerMemberNameFromURL(HttpServletRequest request)
{
String name = null;
String requestURI = request.getRequestURI();
int idx = requestURI.lastIndexOf(";");
if (idx != -1)
{
String requestURISuffix = requestURI.substring(idx);
if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
{
name = extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
}
}
return name;
}
private String extractBalancerMemberNameFromSessionId(String sessionId)
{
String name = null;
int idx = sessionId.lastIndexOf(".");
if (idx != -1)
{
String sessionIdSuffix = sessionId.substring(idx + 1);
name = (sessionIdSuffix.length() > 0)?sessionIdSuffix:null;
}
return name;
}
@Override
protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
{
if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
{
HttpURI locationURI = new HttpURI(headerValue);
if (isAbsoluteLocation(locationURI) && isBackendLocation(locationURI))
{
Request jettyRequest = (Request)request;
URI reverseUri;
try
{
reverseUri = new URI(jettyRequest.getRootURL().append(locationURI.getCompletePath()).toString()).normalize();
return reverseUri.toURL().toString();
}
catch (Exception e)
{
_log.warn("Not filtering header response",e);
return headerValue;
}
}
}
return headerValue;
}
private boolean isBackendLocation(HttpURI locationURI)
{
for (BalancerMember balancerMember : _balancerMembers)
{
HttpURI backendURI = balancerMember.getBackendURI();
if (backendURI.getHost().equals(locationURI.getHost()) && backendURI.getScheme().equals(locationURI.getScheme())
&& backendURI.getPort() == locationURI.getPort())
{
return true;
}
}
return false;
}
private boolean isAbsoluteLocation(HttpURI locationURI)
{
return locationURI.getHost() != null;
}
@Override
public String getHostHeader()
{
throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
}
@Override
public void setHostHeader(String hostHeader)
{
throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
}
@Override
public boolean validateDestination(String host, String path)
{
return true;
}
} }

View File

@ -0,0 +1,62 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
public class DownstreamConnection extends ProxyConnection
{
private final ByteBuffer buffer;
public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context, ConnectHandler connectHandler, ByteBuffer buffer)
{
super(endPoint, executor, bufferPool, context, connectHandler);
this.buffer = buffer;
}
@Override
public void onOpen()
{
super.onOpen();
final int remaining = buffer.remaining();
write(buffer, new Callback<Void>()
{
@Override
public void completed(Void context)
{
LOG.debug("{} wrote initial {} bytes to server", DownstreamConnection.this, remaining);
fillInterested();
}
@Override
public void failed(Void context, Throwable x)
{
LOG.debug(this + " failed to write initial " + remaining + " bytes to server", x);
close();
getConnection().close();
}
});
}
}

View File

@ -0,0 +1,171 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.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.ForkInvoker;
import org.eclipse.jetty.util.log.Logger;
public abstract class ProxyConnection extends AbstractConnection
{
protected static final Logger LOG = ConnectHandler.LOG;
private final ForkInvoker<ByteBuffer> invoker = new ProxyForkInvoker();
private final ByteBufferPool bufferPool;
private final ConcurrentMap<String, Object> context;
private final ConnectHandler connectHandler;
private Connection connection;
protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context, ConnectHandler connectHandler)
{
super(endp, executor);
this.bufferPool = bufferPool;
this.context = context;
this.connectHandler = connectHandler;
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public ConcurrentMap<String, Object> getContext()
{
return context;
}
public ConnectHandler getConnectHandler()
{
return connectHandler;
}
public Connection getConnection()
{
return connection;
}
public void setConnection(Connection connection)
{
this.connection = connection;
}
@Override
public void onFillable()
{
ByteBuffer buffer = getByteBufferPool().acquire(getInputBufferSize(), true);
fill(buffer);
}
private void fill(final ByteBuffer buffer)
{
try
{
final int filled = connectHandler.read(getEndPoint(), buffer, getContext());
LOG.debug("{} filled {} bytes", this, filled);
if (filled > 0)
{
write(buffer, new Callback<Void>()
{
@Override
public void completed(Void context)
{
LOG.debug("{} wrote {} bytes", this, filled);
buffer.clear();
invoker.invoke(buffer);
}
@Override
public void failed(Void context, Throwable x)
{
LOG.debug(this + " failed to write " + filled + " bytes", x);
bufferPool.release(buffer);
connection.close();
}
});
}
else if (filled == 0)
{
bufferPool.release(buffer);
fillInterested();
}
else
{
bufferPool.release(buffer);
connection.getEndPoint().shutdownOutput();
}
}
catch (IOException x)
{
LOG.debug(this + " could not fill", x);
bufferPool.release(buffer);
close();
connection.close();
}
}
protected void write(ByteBuffer buffer, Callback<Void> callback)
{
LOG.debug("{} writing {} bytes", this, buffer.remaining());
connectHandler.write(getConnection().getEndPoint(), buffer, context, callback);
}
@Override
public String toString()
{
return String.format("%s[l:%d<=>r:%d]",
super.toString(),
getEndPoint().getLocalAddress().getPort(),
getEndPoint().getRemoteAddress().getPort());
}
private class ProxyForkInvoker extends ForkInvoker<ByteBuffer>
{
private ProxyForkInvoker()
{
super(4);
}
@Override
public void fork(final ByteBuffer buffer)
{
getExecutor().execute(new Runnable()
{
@Override
public void run()
{
call(buffer);
}
});
}
@Override
public void call(ByteBuffer buffer)
{
fill(buffer);
}
}
}

View File

@ -0,0 +1,43 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
public class UpstreamConnection extends ProxyConnection
{
private ConnectHandler.ConnectContext connectContext;
public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectHandler connectHandler, ConnectHandler.ConnectContext connectContext)
{
super(endPoint, executor, bufferPool, connectContext.getContext(), connectHandler);
this.connectContext = connectContext;
}
@Override
public void onOpen()
{
super.onOpen();
getConnectHandler().onConnectSuccess(connectContext, this);
fillInterested();
}
}

View File

@ -1,164 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.servlets;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.proxy.BalancerServlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.SelectChannelConnector;
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
public abstract class AbstractBalancerServletTest
{
private boolean _stickySessions;
private Server _node1;
private Server _node2;
private Server _balancerServer;
private HttpClient _httpClient;
@Before
public void setUp() throws Exception
{
_httpClient = new HttpClient();
_httpClient.registerListener("org.eclipse.jetty.client.RedirectListener");
_httpClient.start();
}
@After
public void tearDown() throws Exception
{
stopServer(_node1);
stopServer(_node2);
stopServer(_balancerServer);
_httpClient.stop();
}
private void stopServer(Server server)
{
try
{
server.stop();
}
catch (Exception e)
{
// Do nothing
}
}
protected void setStickySessions(boolean stickySessions)
{
_stickySessions = stickySessions;
}
protected void startBalancer(Class<? extends HttpServlet> httpServletClass) throws Exception
{
_node1 = createServer(new ServletHolder(httpServletClass.newInstance()),"/pipo","/molo/*");
setSessionIdManager(_node1,"node1");
_node1.start();
_node2 = createServer(new ServletHolder(httpServletClass.newInstance()),"/pipo","/molo/*");
setSessionIdManager(_node2,"node2");
_node2.start();
BalancerServlet balancerServlet = new BalancerServlet();
ServletHolder balancerServletHolder = new ServletHolder(balancerServlet);
balancerServletHolder.setInitParameter("StickySessions",String.valueOf(_stickySessions));
balancerServletHolder.setInitParameter("ProxyPassReverse","true");
balancerServletHolder.setInitParameter("BalancerMember." + "node1" + ".ProxyTo","http://localhost:" + getServerPort(_node1));
balancerServletHolder.setInitParameter("BalancerMember." + "node2" + ".ProxyTo","http://localhost:" + getServerPort(_node2));
_balancerServer = createServer(balancerServletHolder,"/pipo","/molo/*");
_balancerServer.start();
}
private Server createServer(ServletHolder servletHolder, String appContext, String servletUrlPattern)
{
Server server = new Server();
SelectChannelConnector httpConnector = new SelectChannelConnector(server);
server.addConnector(httpConnector);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath(appContext);
server.setHandler(context);
context.addServlet(servletHolder,servletUrlPattern);
return server;
}
private void setSessionIdManager(Server node, String nodeName)
{
HashSessionIdManager sessionIdManager = new HashSessionIdManager();
sessionIdManager.setWorkerName(nodeName);
node.setSessionIdManager(sessionIdManager);
}
private int getServerPort(Server server)
{
return ((Connector.NetConnector)server.getConnectors()[0]).getLocalPort();
}
protected byte[] sendRequestToBalancer(String requestUri) throws IOException, InterruptedException
{
ContentExchange exchange = new ContentExchange()
{
@Override
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
{
// Cookie persistence
if (name.toString().equals("Set-Cookie"))
{
String cookieVal = value.toString();
if (cookieVal.startsWith("JSESSIONID="))
{
String jsessionid = cookieVal.split(";")[0].substring("JSESSIONID=".length());
_httpClient.getDestination(getAddress(),false).addCookie(new HttpCookie("JSESSIONID",jsessionid));
}
}
}
};
exchange.setURL("http://localhost:" + getServerPort(_balancerServer) + "/pipo/molo/" + requestUri);
exchange.setMethod(HttpMethods.GET);
_httpClient.send(exchange);
exchange.waitForDone();
return exchange.getResponseContentBytes();
}
}

View File

@ -16,203 +16,64 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.server.handler; package org.eclipse.jetty.proxy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.proxy.ConnectHandler;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.SelectChannelConnector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.junit.AfterClass; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
import org.junit.After;
/**
* @version $Revision$ $Date$
*/
public abstract class AbstractConnectHandlerTest public abstract class AbstractConnectHandlerTest
{ {
protected static Server server; protected Server server;
protected static Connector.NetConnector serverConnector; protected ServerConnector serverConnector;
protected static Server proxy; protected Server proxy;
protected static Connector proxyConnector; protected Connector proxyConnector;
protected ConnectHandler connectHandler;
protected static void startServer(Connector.NetConnector connector, Handler handler) throws Exception protected void prepareProxy() throws Exception
{
server = new Server();
serverConnector = connector;
server.addConnector(serverConnector);
server.setHandler(handler);
server.start();
}
protected static void startProxy() throws Exception
{ {
proxy = new Server(); proxy = new Server();
proxyConnector = new SelectChannelConnector(); proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector); proxy.addConnector(proxyConnector);
proxy.setHandler(new ConnectHandler()); connectHandler = new ConnectHandler();
proxy.setHandler(connectHandler);
proxy.start(); proxy.start();
} }
@AfterClass @After
public static void stop() throws Exception public void dispose() throws Exception
{ {
stopProxy(); disposeServer();
stopServer(); disposeProxy();
} }
protected static void stopServer() throws Exception protected void disposeServer() throws Exception
{ {
server.stop(); server.stop();
server.join();
} }
protected static void stopProxy() throws Exception protected void disposeProxy() throws Exception
{ {
proxy.stop(); proxy.stop();
proxy.join();
} }
protected Response readResponse(BufferedReader reader) throws IOException protected SimpleHttpResponse readResponse(BufferedReader reader) throws IOException
{ {
// Simplified parser for HTTP responses return new SimpleHttpParser().readResponse(reader);
String line = reader.readLine();
if (line == null)
throw new EOFException();
Matcher responseLine = Pattern.compile("HTTP/1\\.1\\s+(\\d+)").matcher(line);
assertTrue(responseLine.lookingAt());
String code = responseLine.group(1);
Map<String, String> headers = new LinkedHashMap<String, String>();
while ((line = reader.readLine()) != null)
{
if (line.trim().length() == 0)
break;
Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line);
assertTrue(header.lookingAt());
String headerName = header.group(1);
String headerValue = header.group(2);
headers.put(headerName.toLowerCase(Locale.ENGLISH), headerValue.toLowerCase(Locale.ENGLISH));
}
StringBuilder body;
if (headers.containsKey("content-length"))
{
int readLen = 0;
int length = Integer.parseInt(headers.get("content-length"));
body=new StringBuilder(length);
try
{
for (int i = 0; i < length; ++i)
{
char c = (char)reader.read();
body.append(c);
readLen++;
}
}
catch (SocketTimeoutException e)
{
System.err.printf("Read %,d bytes (out of an expected %,d bytes)%n",readLen,length);
throw e;
}
}
else if ("chunked".equals(headers.get("transfer-encoding")))
{
body = new StringBuilder(64*1024);
while ((line = reader.readLine()) != null)
{
if ("0".equals(line))
{
line = reader.readLine();
assertEquals("", line);
break;
}
try
{
Thread.sleep(5);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
int length = Integer.parseInt(line, 16);
for (int i = 0; i < length; ++i)
{
char c = (char)reader.read();
body.append(c);
}
line = reader.readLine();
assertEquals("", line);
}
}
else throw new IllegalStateException();
return new Response(code, headers, body.toString().trim());
} }
protected Socket newSocket() throws IOException protected Socket newSocket() throws IOException
{ {
Socket socket = new Socket("localhost", ((Connector.NetConnector)proxyConnector).getLocalPort()); Socket socket = new Socket("localhost", ((NetworkConnector)proxyConnector).getLocalPort());
socket.setSoTimeout(10000); socket.setSoTimeout(5000);
return socket; return socket;
} }
protected class Response
{
private final String code;
private final Map<String, String> headers;
private final String body;
private Response(String code, Map<String, String> headers, String body)
{
this.code = code;
this.headers = headers;
this.body = body;
}
public String getCode()
{
return code;
}
public Map<String, String> getHeaders()
{
return headers;
}
public String getBody()
{
return body;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append(code).append("\r\n");
for (Map.Entry<String, String> entry : headers.entrySet())
builder.append(entry.getKey()).append(": ").append(entry.getValue()).append("\r\n");
builder.append("\r\n");
builder.append(body);
return builder.toString();
}
}
} }

View File

@ -1,53 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
public class AsyncProxyServer
{
public static void main(String[] args)
throws Exception
{
Server server = new Server();
Connector connector=new SelectChannelConnector();
connector.setPort(8888);
server.setConnectors(new Connector[]{connector});
ServletHandler handler=new ServletHandler();
server.setHandler(handler);
//FilterHolder gzip = handler.addFilterWithMapping("org.eclipse.jetty.servlet.GzipFilter","/*",EnumSet.of(DispatcherType.REQUEST,DispatcherType.ASYNC));
//gzip.setAsyncSupported(true);
//gzip.setInitParameter("minGzipSize","256");
ServletHolder proxy = handler.addServletWithMapping("org.eclipse.jetty.servlets.ProxyServlet","/");
proxy.setAsyncSupported(true);
server.start();
server.join();
}
}

View File

@ -16,69 +16,148 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.servlets; package org.eclipse.jetty.proxy;
import static org.junit.Assert.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.session.HashSessionIdManager;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
/** public class BalancerServletTest
*
*/
public class BalancerServletTest extends AbstractBalancerServletTest
{ {
private static final String CONTEXT_PATH = "/context";
private static final String SERVLET_PATH = "/mapping";
private boolean stickySessions;
private Server server1;
private Server server2;
private Server balancer;
private HttpClient client;
@Before
public void prepare() throws Exception
{
client = new HttpClient();
client.start();
}
@After
public void dispose() throws Exception
{
server1.stop();
server2.stop();
balancer.stop();
client.stop();
}
protected void startBalancer(Class<? extends HttpServlet> servletClass) throws Exception
{
server1 = createServer(new ServletHolder(servletClass), "node1");
server1.start();
server2 = createServer(new ServletHolder(servletClass), "node2");
server2.start();
ServletHolder balancerServletHolder = new ServletHolder(BalancerServlet.class);
balancerServletHolder.setInitParameter("stickySessions", String.valueOf(stickySessions));
balancerServletHolder.setInitParameter("proxyPassReverse", "true");
balancerServletHolder.setInitParameter("balancerMember." + "node1" + ".proxyTo", "http://localhost:" + getServerPort(server1));
balancerServletHolder.setInitParameter("balancerMember." + "node2" + ".proxyTo", "http://localhost:" + getServerPort(server2));
balancer = createServer(balancerServletHolder, null);
balancer.start();
}
private Server createServer(ServletHolder servletHolder, String nodeName)
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(server, CONTEXT_PATH, ServletContextHandler.SESSIONS);
context.addServlet(servletHolder, SERVLET_PATH + "/*");
if (nodeName != null)
{
HashSessionIdManager sessionIdManager = new HashSessionIdManager();
sessionIdManager.setWorkerName(nodeName);
server.setSessionIdManager(sessionIdManager);
}
return server;
}
private int getServerPort(Server server)
{
return ((NetworkConnector)server.getConnectors()[0]).getLocalPort();
}
protected byte[] sendRequestToBalancer(String path) throws Exception
{
ContentResponse response = client.newRequest("localhost", getServerPort(balancer))
.path(CONTEXT_PATH + SERVLET_PATH + path)
.send()
.get(5, TimeUnit.SECONDS);
return response.getContent();
}
@Test @Test
public void testRoundRobinBalancer() throws Exception public void testRoundRobinBalancer() throws Exception
{ {
setStickySessions(false); stickySessions = false;
startBalancer(CounterServlet.class); startBalancer(CounterServlet.class);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
byte[] responseBytes = sendRequestToBalancer("/"); byte[] responseBytes = sendRequestToBalancer("/roundRobin");
String returnedCounter = readFirstLine(responseBytes); String returnedCounter = readFirstLine(responseBytes);
// RR : response should increment every other request // Counter should increment every other request
String expectedCounter = String.valueOf(i / 2); String expectedCounter = String.valueOf(i / 2);
assertEquals(expectedCounter,returnedCounter); Assert.assertEquals(expectedCounter, returnedCounter);
} }
} }
@Test @Test
public void testStickySessionsBalancer() throws Exception public void testStickySessionsBalancer() throws Exception
{ {
setStickySessions(true); stickySessions = true;
startBalancer(CounterServlet.class); startBalancer(CounterServlet.class);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
byte[] responseBytes = sendRequestToBalancer("/"); byte[] responseBytes = sendRequestToBalancer("/stickySessions");
String returnedCounter = readFirstLine(responseBytes); String returnedCounter = readFirstLine(responseBytes);
// RR : response should increment on each request // Counter should increment every request
String expectedCounter = String.valueOf(i); String expectedCounter = String.valueOf(i);
assertEquals(expectedCounter,returnedCounter); Assert.assertEquals(expectedCounter, returnedCounter);
} }
} }
@Test @Test
public void testProxyPassReverse() throws Exception public void testProxyPassReverse() throws Exception
{ {
setStickySessions(false); stickySessions = false;
startBalancer(RelocationServlet.class); startBalancer(RelocationServlet.class);
byte[] responseBytes = sendRequestToBalancer("/index.html");
byte[] responseBytes = sendRequestToBalancer("index.html");
String msg = readFirstLine(responseBytes); String msg = readFirstLine(responseBytes);
assertEquals("success",msg); Assert.assertEquals("success", msg);
} }
private String readFirstLine(byte[] responseBytes) throws IOException private String readFirstLine(byte[] responseBytes) throws IOException
@ -87,17 +166,9 @@ public class BalancerServletTest extends AbstractBalancerServletTest
return reader.readLine(); return reader.readLine();
} }
@SuppressWarnings("serial")
public static final class CounterServlet extends HttpServlet public static final class CounterServlet extends HttpServlet
{ {
private final AtomicInteger counter = new AtomicInteger();
private int counter;
@Override
public void init() throws ServletException
{
counter = 0;
}
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
@ -105,11 +176,10 @@ public class BalancerServletTest extends AbstractBalancerServletTest
// Force session creation // Force session creation
req.getSession(); req.getSession();
resp.setContentType("text/plain"); resp.setContentType("text/plain");
resp.getWriter().println(counter++); resp.getWriter().print(counter.getAndIncrement());
} }
} }
@SuppressWarnings("serial")
public static final class RelocationServlet extends HttpServlet public static final class RelocationServlet extends HttpServlet
{ {
@Override @Override
@ -117,19 +187,14 @@ public class BalancerServletTest extends AbstractBalancerServletTest
{ {
if (req.getRequestURI().endsWith("/index.html")) if (req.getRequestURI().endsWith("/index.html"))
{ {
resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo%20molo"); resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo+molo");
return;
}
resp.setContentType("text/plain");
if ("pipo molo".equals(req.getParameter("secret")))
{
resp.getWriter().println("success");
} }
else else
{ {
resp.getWriter().println("failure"); resp.setContentType("text/plain");
if ("pipo molo".equals(req.getParameter("secret")))
resp.getWriter().println("success");
} }
} }
} }
} }

View File

@ -16,7 +16,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.server.handler; package org.eclipse.jetty.proxy;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -25,65 +25,56 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.BeforeClass; import org.junit.Assert;
import org.junit.Ignore; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* @version $Revision$ $Date$
*/
@Ignore
public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
{ {
@BeforeClass private SslContextFactory sslContextFactory;
public static void init() throws Exception
@Before
public void prepare() throws Exception
{ {
SslSelectChannelConnector connector = new SslSelectChannelConnector(); sslContextFactory = new SslContextFactory();
connector.setMaxIdleTime(3600000); // TODO remove String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore.jks").getAbsolutePath();
sslContextFactory.setKeyStorePath(keyStorePath);
String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath(); sslContextFactory.setKeyStorePassword("storepwd");
SslContextFactory cf = connector.getSslContextFactory(); String trustStorePath = MavenTestingUtils.getTestResourceFile("truststore.jks").getAbsolutePath();
cf.setKeyStorePath(keyStorePath); sslContextFactory.setTrustStorePath(trustStorePath);
cf.setKeyStorePassword("storepwd"); sslContextFactory.setTrustStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd"); server = new Server();
serverConnector = new ServerConnector(server, sslContextFactory);
startServer(connector, new ServerHandler()); server.addConnector(serverConnector);
startProxy(); server.setHandler(new ServerHandler());
server.start();
prepareProxy();
} }
@Test @Test
public void testGETRequest() throws Exception public void testGETRequest() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
socket.setSoTimeout(3600000); // TODO remove
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -92,52 +83,41 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
System.err.println(response); Assert.assertEquals("200", response.getCode());
assertEquals("200", response.getCode());
// Be sure the buffered input does not have anything buffered // Be sure the buffered input does not have anything buffered
assertFalse(input.ready()); Assert.assertFalse(input.ready());
// Upgrade the socket to SSL // Upgrade the socket to SSL
SSLSocket sslSocket = wrapSocket(socket); try (SSLSocket sslSocket = wrapSocket(socket))
try
{ {
output = sslSocket.getOutputStream(); output = sslSocket.getOutputStream();
input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
request = request =
"GET /echo HTTP/1.1\r\n" + "GET /echo HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
output.write(request.getBytes("UTF-8")); output.write(request.getBytes("UTF-8"));
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
} }
finally
{
sslSocket.close();
}
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testPOSTRequests() throws Exception public void testPOSTRequests() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -146,15 +126,14 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
// Be sure the buffered input does not have anything buffered // Be sure the buffered input does not have anything buffered
assertFalse(input.ready()); Assert.assertFalse(input.ready());
// Upgrade the socket to SSL // Upgrade the socket to SSL
SSLSocket sslSocket = wrapSocket(socket); try (SSLSocket sslSocket = wrapSocket(socket))
try
{ {
output = sslSocket.getOutputStream(); output = sslSocket.getOutputStream();
input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream())); input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
@ -171,25 +150,16 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody()); Assert.assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody());
} }
} }
finally
{
sslSocket.close();
}
}
finally
{
socket.close();
} }
} }
private SSLSocket wrapSocket(Socket socket) throws Exception private SSLSocket wrapSocket(Socket socket) throws Exception
{ {
SSLContext sslContext = SSLContext.getInstance("SSLv3"); SSLContext sslContext = sslContextFactory.getSslContext();
sslContext.init(null, new TrustManager[]{new AlwaysTrustManager()}, new SecureRandom());
SSLSocketFactory socketFactory = sslContext.getSocketFactory(); SSLSocketFactory socketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
sslSocket.setUseClientMode(true); sslSocket.setUseClientMode(true);
@ -197,22 +167,6 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
return sslSocket; return sslSocket;
} }
private class AlwaysTrustManager implements X509TrustManager
{
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
{
}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException
{
}
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[]{};
}
}
private static class ServerHandler extends AbstractHandler private static class ServerHandler extends AbstractHandler
{ {
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
@ -229,7 +183,7 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = httpRequest.getInputStream(); InputStream input = httpRequest.getInputStream();
int read = -1; int read;
while ((read = input.read()) >= 0) while ((read = input.read()) >= 0)
baos.write(read); baos.write(read);
baos.close(); baos.close();

View File

@ -16,10 +16,7 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.server.handler; package org.eclipse.jetty.proxy;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -29,51 +26,49 @@ import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.nio.ByteBuffer;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.channels.SocketChannel; import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.proxy.ConnectHandler;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.SelectChannelConnector; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.Callback;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
/**
* @version $Revision$ $Date$
*/
@Ignore
public class ConnectHandlerTest extends AbstractConnectHandlerTest public class ConnectHandlerTest extends AbstractConnectHandlerTest
{ {
@BeforeClass @Before
public static void init() throws Exception public void prepare() throws Exception
{ {
startServer(new SelectChannelConnector(), new ServerHandler()); server = new Server();
startProxy(); serverConnector = new ServerConnector(server);
server.addConnector(serverConnector);
server.setHandler(new ServerHandler());
server.start();
prepareProxy();
} }
@Test @Test
public void testCONNECT() throws Exception public void testCONNECT() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -82,26 +77,20 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndGET() throws Exception public void testCONNECTAndGET() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
socket.setSoTimeout(30000);
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -110,8 +99,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
request = "" + request = "" +
"GET /echo" + " HTTP/1.1\r\n" + "GET /echo" + " HTTP/1.1\r\n" +
@ -121,38 +110,229 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
}
finally
{
socket.close();
} }
} }
@Test
public void testProxyWhiteList() throws Exception
{
int port = serverConnector.getLocalPort();
String hostPort = "127.0.0.1:" + port;
connectHandler.getWhiteListHosts().add(hostPort);
// Try with the wrong host
String request = "" +
"CONNECT localhost:" + port + " HTTP/1.1\r\n" +
"Host: localhost:" + port + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 403 from the CONNECT request
SimpleHttpResponse response = readResponse(input);
Assert.assertEquals("403", response.getCode());
// Socket should be closed
Assert.assertEquals(-1, input.read());
}
// Try again with the right host
request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 from the CONNECT request
SimpleHttpResponse response = readResponse(input);
Assert.assertEquals("200", response.getCode());
request = "" +
"GET /echo" + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"\r\n";
output.write(request.getBytes("UTF-8"));
output.flush();
response = readResponse(input);
Assert.assertEquals("200", response.getCode());
Assert.assertEquals("GET /echo", response.getBody());
}
}
@Test
public void testProxyBlackList() throws Exception
{
int port = serverConnector.getLocalPort();
String hostPort = "localhost:" + port;
connectHandler.getBlackListHosts().add(hostPort);
// Try with the wrong host
String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 403 from the CONNECT request
SimpleHttpResponse response = readResponse(input);
Assert.assertEquals("403", response.getCode());
// Socket should be closed
Assert.assertEquals(-1, input.read());
}
// Try again with the right host
request = "" +
"CONNECT 127.0.0.1:" + port + " HTTP/1.1\r\n" +
"Host: 127.0.0.1:" + port + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 from the CONNECT request
SimpleHttpResponse response = readResponse(input);
Assert.assertEquals("200", response.getCode());
request = "" +
"GET /echo" + " HTTP/1.1\r\n" +
"Host: 127.0.0.1:" + port + "\r\n" +
"\r\n";
output.write(request.getBytes("UTF-8"));
output.flush();
response = readResponse(input);
Assert.assertEquals("200", response.getCode());
Assert.assertEquals("GET /echo", response.getBody());
}
}
@Test
public void testProxyAuthentication() throws Exception
{
disposeProxy();
connectHandler = new ConnectHandler()
{
@Override
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
{
String proxyAuthorization = request.getHeader("Proxy-Authorization");
if (proxyAuthorization == null)
{
response.setHeader("Proxy-Authenticate", "Basic realm=\"test\"");
return false;
}
String b64 = proxyAuthorization.substring("Basic ".length());
String credentials = B64Code.decode(b64, "UTF-8");
return "test:test".equals(credentials);
}
};
proxy.setHandler(connectHandler);
proxy.start();
int port = serverConnector.getLocalPort();
String hostPort = "localhost:" + port;
// Try without authentication
String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 407 from the CONNECT request
SimpleHttpResponse response = readResponse(input);
Assert.assertEquals("407", response.getCode());
Assert.assertTrue(response.getHeaders().containsKey("Proxy-Authenticate".toLowerCase(Locale.ENGLISH)));
// Socket should be closed
Assert.assertEquals(-1, input.read());
}
// Try with authentication
String credentials = "Basic " + B64Code.encode("test:test");
request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"Proxy-Authorization: " + credentials + "\r\n" +
"\r\n";
try (Socket socket = newSocket())
{
OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
output.write(request.getBytes("UTF-8"));
output.flush();
// Expect 200 from the CONNECT request
SimpleHttpResponse response = readResponse(input);
Assert.assertEquals("200", response.getCode());
request = "" +
"GET /echo" + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" +
"\r\n";
output.write(request.getBytes("UTF-8"));
output.flush();
response = readResponse(input);
Assert.assertEquals("200", response.getCode());
Assert.assertEquals("GET /echo", response.getBody());
}
}
@Test @Test
public void testCONNECTBadHostPort() throws Exception public void testCONNECTBadHostPort() throws Exception
{ {
String invalidHostname = "AMAZEBALLS_BADHOST.webtide.com"; String invalidHostname = "badHost.webtide.com";
try try
{ {
InetAddress addr = InetAddress.getByName(invalidHostname); InetAddress address = InetAddress.getByName(invalidHostname);
StringBuilder err = new StringBuilder(); StringBuilder err = new StringBuilder();
err.append("DNS Hijacking detected: "); err.append("DNS Hijacking detected: ");
err.append(invalidHostname).append(" should have not returned a valid IP address ["); err.append(invalidHostname).append(" should have not returned a valid IP address [");
err.append(addr.getHostAddress()).append("]. "); err.append(address.getHostAddress()).append("]. ");
err.append("Fix your DNS provider to have this test pass."); err.append("Fix your DNS provider to have this test pass.");
err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking"); err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking");
Assert.assertNull(err.toString(), addr); Assert.assertNull(err.toString(), address);
} }
catch (UnknownHostException e) catch (UnknownHostException e)
{ {
// expected path // expected path
} }
String hostPort = String.format("%s:%d",invalidHostname,serverConnector.getLocalPort()); String hostPort = String.format("%s:%d", invalidHostname, serverConnector.getLocalPort());
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
@ -168,8 +348,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 500 OK from the CONNECT request // Expect 500 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("Response Code", "500", response.getCode()); Assert.assertEquals("Response Code", "500", response.getCode());
} }
finally finally
{ {
@ -180,13 +360,12 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
@Test @Test
public void testCONNECT10AndGET() throws Exception public void testCONNECT10AndGET() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.0\r\n" + "CONNECT " + hostPort + " HTTP/1.0\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -195,8 +374,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
request = "" + request = "" +
"GET /echo" + " HTTP/1.1\r\n" + "GET /echo" + " HTTP/1.1\r\n" +
@ -206,19 +385,15 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndGETPipelined() throws Exception public void testCONNECTAndGETPipelined() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
@ -226,8 +401,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
"GET /echo" + " HTTP/1.1\r\n" + "GET /echo" + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -236,30 +410,25 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
// The pipelined request must have gone up to the server as is // The pipelined request must have gone up to the server as is
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndMultipleGETs() throws Exception public void testCONNECTAndMultipleGETs() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -268,8 +437,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
for (int i = 0; i < 10; ++i) for (int i = 0; i < 10; ++i)
{ {
@ -281,26 +450,21 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
} }
} }
finally
{
socket.close();
}
} }
@Test @Test
public void testCONNECTAndGETServerStop() throws Exception public void testCONNECTAndGETServerStop() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -309,8 +473,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
request = "" + request = "" +
"GET /echo HTTP/1.1\r\n" + "GET /echo HTTP/1.1\r\n" +
@ -320,33 +484,26 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
// Idle server is shut down // Idle server is shut down
stopServer(); disposeServer();
int read = input.read(); int read = input.read();
assertEquals(-1, read); Assert.assertEquals(-1, read);
}
finally
{
socket.close();
// Restart the server for the next test
server.start();
} }
} }
@Test @Test
public void testCONNECTAndGETAndServerSideClose() throws Exception public void testCONNECTAndGETAndServerSideClose() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -355,8 +512,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
request = "" + request = "" +
"GET /close HTTP/1.1\r\n" + "GET /close HTTP/1.1\r\n" +
@ -366,24 +523,19 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
int read = input.read(); int read = input.read();
assertEquals(-1, read); Assert.assertEquals(-1, read);
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndPOSTAndGET() throws Exception public void testCONNECTAndPOSTAndGET() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -392,8 +544,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
request = "" + request = "" +
"POST /echo HTTP/1.1\r\n" + "POST /echo HTTP/1.1\r\n" +
@ -405,8 +557,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("POST /echo\r\nHELLO", response.getBody()); Assert.assertEquals("POST /echo\r\nHELLO", response.getBody());
request = "" + request = "" +
"GET /echo" + " HTTP/1.1\r\n" + "GET /echo" + " HTTP/1.1\r\n" +
@ -416,34 +568,21 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndPOSTWithBigBody() throws Exception public void testCONNECTAndPOSTWithBigBody() throws Exception
{ {
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
// fails under windows and occasionally on mac due to OOME
boolean stress = Boolean.getBoolean( "STRESS" );
if (!stress)
{
return;
}
// Log.getLogger(ConnectHandler.class).setDebugEnabled(true);
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -452,8 +591,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
StringBuilder body = new StringBuilder(); StringBuilder body = new StringBuilder();
String chunk = "0123456789ABCDEF"; String chunk = "0123456789ABCDEF";
@ -470,12 +609,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("POST /echo\r\n" + body, response.getBody()); Assert.assertEquals("POST /echo\r\n" + body, response.getBody());
}
finally
{
socket.close();
} }
} }
@ -486,54 +621,46 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
final String contextValue = "contextValue"; final String contextValue = "contextValue";
// Replace the default ProxyHandler with a subclass to test context information passing // Replace the default ProxyHandler with a subclass to test context information passing
stopProxy(); disposeProxy();
proxy.setHandler(new ConnectHandler() proxy.setHandler(new ConnectHandler()
{ {
@Override @Override
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
{ {
request.setAttribute(contextKey, contextValue); request.setAttribute(contextKey, contextValue);
return super.handleAuthentication(request, response, address); return super.handleAuthentication(request, response, address);
} }
@Override
protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException
{
assertEquals(contextValue, request.getAttribute(contextKey));
return super.connect(request, host, port);
}
@Override @Override
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context) protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
{ {
// Transfer data from the HTTP request to the connection context // Transfer data from the HTTP request to the connection context
assertEquals(contextValue, request.getAttribute(contextKey)); Assert.assertEquals(contextValue, request.getAttribute(contextKey));
context.put(contextKey, request.getAttribute(contextKey)); context.put(contextKey, request.getAttribute(contextKey));
} }
@Override @Override
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException
{ {
assertEquals(contextValue, context.get(contextKey)); Assert.assertEquals(contextValue, context.get(contextKey));
return super.read(endPoint, buffer, context); return super.read(endPoint, buffer, context);
} }
@Override @Override
protected int write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException protected void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context, Callback<Void> callback)
{ {
assertEquals(contextValue, context.get(contextKey)); Assert.assertEquals(contextValue, context.get(contextKey));
return super.write(endPoint, buffer, context); super.write(endPoint, buffer, context, callback);
} }
}); });
proxy.start(); proxy.start();
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort(); String hostPort = "localhost:" + serverConnector.getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -542,8 +669,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
String body = "0123456789ABCDEF"; String body = "0123456789ABCDEF";
request = "" + request = "" +
@ -556,22 +683,15 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("POST /echo\r\n" + body, response.getBody()); Assert.assertEquals("POST /echo\r\n" + body, response.getBody());
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception
{ {
// TODO needs to be further investigated String hostPort = "localhost:" + serverConnector.getLocalPort();
assumeTrue(!OS.IS_OSX);
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
@ -579,8 +699,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
"GET /echo" + " HTTP/1.1\r\n" + "GET /echo" + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -590,33 +709,25 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
socket.shutdownOutput(); socket.shutdownOutput();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
// The pipelined request must have gone up to the server as is // The pipelined request must have gone up to the server as is
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
}
finally
{
socket.close();
} }
} }
@Test @Test
public void testCONNECTAndGETAndOutputShutdown() throws Exception public void testCONNECTAndGETAndOutputShutdown() throws Exception
{ {
// TODO needs to be further investigated String hostPort = "localhost:" + serverConnector.getLocalPort();
assumeTrue(!OS.IS_OSX);
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
String request = "" + String request = "" +
"CONNECT " + hostPort + " HTTP/1.1\r\n" + "CONNECT " + hostPort + " HTTP/1.1\r\n" +
"Host: " + hostPort + "\r\n" + "Host: " + hostPort + "\r\n" +
"\r\n"; "\r\n";
Socket socket = newSocket(); try (Socket socket = newSocket())
try
{ {
OutputStream output = socket.getOutputStream(); OutputStream output = socket.getOutputStream();
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
@ -625,8 +736,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
output.flush(); output.flush();
// Expect 200 OK from the CONNECT request // Expect 200 OK from the CONNECT request
Response response = readResponse(input); SimpleHttpResponse response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
request = "" + request = "" +
"GET /echo" + " HTTP/1.1\r\n" + "GET /echo" + " HTTP/1.1\r\n" +
@ -638,12 +749,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
// The pipelined request must have gone up to the server as is // The pipelined request must have gone up to the server as is
response = readResponse(input); response = readResponse(input);
assertEquals("200", response.getCode()); Assert.assertEquals("200", response.getCode());
assertEquals("GET /echo", response.getBody()); Assert.assertEquals("GET /echo", response.getBody());
}
finally
{
socket.close();
} }
} }
@ -654,31 +761,36 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
request.setHandled(true); request.setHandled(true);
String uri = httpRequest.getRequestURI(); String uri = httpRequest.getRequestURI();
if ("/echo".equals(uri)) switch (uri)
{ {
StringBuilder builder = new StringBuilder(); case "/echo":
builder.append(httpRequest.getMethod()).append(" ").append(uri); {
if (httpRequest.getQueryString() != null) StringBuilder builder = new StringBuilder();
builder.append("?").append(httpRequest.getQueryString()); builder.append(httpRequest.getMethod()).append(" ").append(uri);
if (httpRequest.getQueryString() != null)
builder.append("?").append(httpRequest.getQueryString());
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream input = httpRequest.getInputStream(); InputStream input = httpRequest.getInputStream();
int read = -1; int read;
while ((read = input.read()) >= 0) while ((read = input.read()) >= 0)
baos.write(read); baos.write(read);
baos.close(); baos.close();
ServletOutputStream output = httpResponse.getOutputStream(); ServletOutputStream output = httpResponse.getOutputStream();
output.println(builder.toString()); output.println(builder.toString());
output.write(baos.toByteArray()); output.write(baos.toByteArray());
} break;
else if ("/close".equals(uri)) }
{ case "/close":
request.getHttpChannel().getConnection().getEndPoint().close(); {
} request.getHttpChannel().getEndPoint().close();
else break;
{ }
throw new ServletException(); default:
{
throw new ServletException();
}
} }
} }
} }

View File

@ -1,77 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.proxy;
import static org.mockito.Mockito.when;
import static org.mockito.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import org.eclipse.jetty.io.EndPoint;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
/* ------------------------------------------------------------ */
/**
*/
@RunWith(MockitoJUnitRunner.class)
public class ConnectHandlerUnitTest
{
@Mock
private EndPoint endPoint;
// TODO update for jetty-9
@Test
@Ignore
public void testPartialWritesWithNonFullBuffer() throws IOException
{
/*
ConnectHandler connectHandler = new ConnectHandler();
final byte[] bytes = "foo bar".getBytes();
Buffer buffer = new ByteArrayBuffer(bytes.length * 2);
buffer.put(bytes);
when(endPoint.flush(buffer)).thenAnswer(new Answer<Object>()
{
public Object answer(InvocationOnMock invocation)
{
Object[] args = invocation.getArguments();
Buffer buffer = (Buffer)args[0];
int skip = bytes.length/2;
buffer.skip(skip);
return skip;
}
});
when(endPoint.blockWritable(anyInt())).thenReturn(true);
// method to test
connectHandler.write(endPoint,buffer,null);
assertThat(buffer.length(),is(0));
*/
}
}

View File

@ -19,20 +19,18 @@
package org.eclipse.jetty.proxy; package org.eclipse.jetty.proxy;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.proxy.ConnectHandler;
import org.eclipse.jetty.server.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.proxy.ProxyServlet;
public class ProxyServer public class ProxyServer
{ {
public static void main(String[] args) throws Exception public static void main(String[] args) throws Exception
{ {
Server server = new Server(); Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector(server); ServerConnector connector = new ServerConnector(server);
connector.setPort(8888); connector.setPort(8080);
server.addConnector(connector); server.addConnector(connector);
HandlerCollection handlers = new HandlerCollection(); HandlerCollection handlers = new HandlerCollection();
@ -41,16 +39,16 @@ public class ProxyServer
// Setup proxy servlet // Setup proxy servlet
ServletContextHandler context = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS); ServletContextHandler context = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class); ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost"); // proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost");
proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/"); // proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
context.addServlet(proxyServlet, "/*"); context.addServlet(proxyServlet, "/*");
// Setup proxy handler to handle CONNECT methods // Setup proxy handler to handle CONNECT methods
ConnectHandler proxy = new ConnectHandler(); // ConnectHandler proxy = new ConnectHandler();
proxy.setWhite(new String[]{"mail.google.com"}); // proxy.setWhite(new String[]{"mail.google.com"});
proxy.addWhite("www.google.com"); // proxy.addWhitelistHost("www.google.com");
handlers.addHandler(proxy); // handlers.addHandler(proxy);
server.start(); server.start();
} }

View File

@ -16,104 +16,510 @@
// ======================================================================== // ========================================================================
// //
package org.eclipse.jetty.servlets; package org.eclipse.jetty.proxy;
import java.io.File; import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.MalformedURLException; import java.net.ConnectException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.client.api.ProxyConfiguration;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.proxy.ProxyServlet; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*; import static java.nio.file.StandardOpenOption.CREATE;
import static org.hamcrest.Matchers.*;
@RunWith(AdvancedRunner.class)
public class ProxyServletTest public class ProxyServletTest
{ {
private Server _server; private static final String PROXIED_HEADER = "X-Proxied";
private Connector _connector; @Rule
private HttpClient _client; public final TestTracker tracker = new TestTracker();
private HttpClient client;
private Server proxy;
private ServerConnector proxyConnector;
private ProxyServlet proxyServlet;
private Server server;
private ServerConnector serverConnector;
public void init(HttpServlet servlet) throws Exception private void prepareProxy(ProxyServlet proxyServlet) throws Exception
{ {
_server = new Server(); proxy = new Server();
proxyConnector = new ServerConnector(proxy);
proxy.addConnector(proxyConnector);
_connector = new SelectChannelConnector(); ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
_server.addConnector(_connector); this.proxyServlet = proxyServlet;
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
HandlerCollection handlers = new HandlerCollection();
_server.setHandler(handlers);
ServletContextHandler proxyCtx = new ServletContextHandler(handlers, "/proxy", ServletContextHandler.NO_SESSIONS);
ServletHolder proxyServletHolder = new ServletHolder(new ProxyServlet()
{
@Override
protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException
{
// Proxies any call to "/proxy" to "/"
return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri.substring("/proxy".length()));
}
});
proxyServletHolder.setInitParameter("timeout", String.valueOf(5 * 60 * 1000L));
proxyCtx.addServlet(proxyServletHolder, "/*"); proxyCtx.addServlet(proxyServletHolder, "/*");
ServletContextHandler appCtx = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS); proxy.start();
client = new HttpClient();
client.setProxyConfiguration(new ProxyConfiguration("localhost", proxyConnector.getLocalPort()));
client.start();
}
private void prepareServer(HttpServlet servlet) throws Exception
{
server = new Server();
serverConnector = new ServerConnector(server);
server.addConnector(serverConnector);
ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false);
ServletHolder appServletHolder = new ServletHolder(servlet); ServletHolder appServletHolder = new ServletHolder(servlet);
appCtx.addServlet(appServletHolder, "/*"); appCtx.addServlet(appServletHolder, "/*");
handlers.addHandler(proxyCtx); server.start();
handlers.addHandler(appCtx);
_server.start();
_client = new HttpClient();
_client.start();
} }
@After @After
public void destroy() throws Exception public void disposeProxy() throws Exception
{ {
if (_client != null) client.stop();
_client.stop(); proxy.stop();
}
if (_server != null) @After
public void disposeServer() throws Exception
{
server.stop();
}
@Test
public void testProxyDown() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new EmptyHttpServlet());
// Shutdown the proxy
proxy.stop();
try
{ {
_server.stop(); client.newRequest("localhost", serverConnector.getLocalPort())
_server.join(); .send()
.get(5, TimeUnit.SECONDS);
Assert.fail();
}
catch (ExecutionException x)
{
Assert.assertThat(x.getCause(), Matchers.instanceOf(ConnectException.class));
} }
} }
@Test @Test
public void testXForwardedHostHeader() throws Exception public void testServerDown() throws Exception
{ {
init(new HttpServlet() prepareProxy(new ProxyServlet());
{ prepareServer(new EmptyHttpServlet());
private static final long serialVersionUID = 1L;
// Shutdown the server
int serverPort = serverConnector.getLocalPort();
server.stop();
ContentResponse response = client.newRequest("localhost", serverPort)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(502, response.getStatus());
}
@Test
public void testServerException() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
throw new ServletException();
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(500, response.getStatus());
}
@Test
public void testProxyWithoutContent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
}
});
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Test
public void testProxyWithResponseContent() throws Exception
{
prepareProxy(new ProxyServlet());
final byte[] content = new byte[1024];
Arrays.fill(content, (byte)'A');
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
resp.getOutputStream().write(content);
}
});
// Request is for the target server
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
Assert.assertArrayEquals(content, response.getContent());
}
@Test
public void testProxyWithRequestContentAndResponseContent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
IO.copy(req.getInputStream(), resp.getOutputStream());
}
});
byte[] content = new byte[1024];
Arrays.fill(content, (byte)'A');
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
Assert.assertArrayEquals(content, response.getContent());
}
@Test
public void testProxyWithBigRequestContentIgnored() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
}
});
byte[] content = new byte[128 * 1024];
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Test
public void testProxyWithBigRequestContentConsumed() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
InputStream input = req.getInputStream();
while (true)
if (input.read() < 0)
break;
}
});
byte[] content = new byte[128 * 1024];
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Slow
@Test
public void testProxyWithBigResponseContentWithSlowReader() throws Exception
{
prepareProxy(new ProxyServlet());
// Create a 6 MiB file
final int length = 6 * 1024;
Path targetTestsDir = MavenTestingUtils.getTargetTestingDir().toPath();
Files.createDirectories(targetTestsDir);
final Path temp = Files.createTempFile(targetTestsDir, "test_", null);
byte[] kb = new byte[1024];
Arrays.fill(kb, (byte)'X');
try (OutputStream output = Files.newOutputStream(temp, CREATE))
{
for (int i = 0; i < length; ++i)
output.write(kb);
}
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
try (InputStream input = Files.newInputStream(temp))
{
IO.copy(input, response.getOutputStream());
}
}
});
Request request = client.newRequest("localhost", serverConnector.getLocalPort()).path("/proxy/test");
final CountDownLatch latch = new CountDownLatch(1);
request.send(new BufferingResponseListener(2 * length * 1024)
{
@Override
public void onContent(Response response, ByteBuffer content)
{
try
{
// Slow down the reader
TimeUnit.MILLISECONDS.sleep(5);
super.onContent(response, content);
}
catch (InterruptedException x)
{
response.abort(x);
}
}
@Override
public void onComplete(Result result)
{
Assert.assertFalse(result.isFailed());
Assert.assertEquals(200, result.getResponse().getStatus());
Assert.assertEquals(length * 1024, getContent().length);
latch.countDown();
}
});
Assert.assertTrue(latch.await(30, TimeUnit.SECONDS));
}
@Test
public void testProxyWithQueryString() throws Exception
{
prepareProxy(new ProxyServlet());
String query = "a=1&b=%E2%82%AC";
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.getOutputStream().print(req.getQueryString());
}
});
ContentResponse response = client.newRequest("http://localhost:" + serverConnector.getLocalPort() + "/?" + query)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(query, response.getContentAsString());
}
@Slow
@Test
public void testProxyLongPoll() throws Exception
{
prepareProxy(new ProxyServlet());
final long timeout = 1000;
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
if (!request.isAsyncStarted())
{
final AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(timeout);
asyncContext.addListener(new AsyncListener()
{
@Override
public void onComplete(AsyncEvent event) throws IOException
{
}
@Override
public void onTimeout(AsyncEvent event) throws IOException
{
if (request.getHeader("Via") != null)
response.addHeader(PROXIED_HEADER, "true");
asyncContext.complete();
}
@Override
public void onError(AsyncEvent event) throws IOException
{
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException
{
}
});
}
}
});
Response response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(2 * timeout, TimeUnit.MILLISECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Slow
@Test
public void testProxyRequestExpired() throws Exception
{
prepareProxy(new ProxyServlet());
final long timeout = 1000;
proxyServlet.setTimeout(timeout);
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
if (request.getHeader("Via") != null)
response.addHeader(PROXIED_HEADER, "true");
try
{
TimeUnit.MILLISECONDS.sleep(2 * timeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
Response response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(3 * timeout, TimeUnit.MILLISECONDS);
Assert.assertEquals(504, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Slow
@Test(expected = TimeoutException.class)
public void testClientRequestExpired() throws Exception
{
prepareProxy(new ProxyServlet());
final long timeout = 1000;
proxyServlet.setTimeout(3 * timeout);
prepareServer(new HttpServlet()
{
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
if (request.getHeader("Via") != null)
response.addHeader(PROXIED_HEADER, "true");
try
{
TimeUnit.MILLISECONDS.sleep(2 * timeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(timeout, TimeUnit.MILLISECONDS);
Assert.fail();
}
@Test
public void testProxyXForwardedHostHeaderIsPresent() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{ {
@ -123,72 +529,233 @@ public class ProxyServletTest
} }
}); });
String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test"; ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort()).get(5, TimeUnit.SECONDS);
ContentExchange exchange = new ContentExchange(); Assert.assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",
exchange.setURL(url); response.getContentAsString(),
_client.send(exchange); Matchers.equalTo("localhost:" + serverConnector.getLocalPort()));
exchange.waitForDone();
assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",exchange.getResponseContent(),equalTo("localhost:"
+ _connector.getLocalPort()));
} }
@Test @Test
public void testBigDownloadWithSlowReader() throws Exception public void testProxyWhiteList() throws Exception
{ {
// Create a 6 MiB file prepareProxy(new ProxyServlet());
final File file = File.createTempFile("test_", null, MavenTestingUtils.getTargetTestingDir()); prepareServer(new EmptyHttpServlet());
file.deleteOnExit(); int port = serverConnector.getLocalPort();
FileOutputStream fos = new FileOutputStream(file); proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port);
byte[] buffer = new byte[1024];
Arrays.fill(buffer, (byte)'X');
for (int i = 0; i < 6 * 1024; ++i)
fos.write(buffer);
fos.close();
init(new HttpServlet() // Try with the wrong host
ContentResponse response = client.newRequest("localhost", port)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(403, response.getStatus());
// Try again with the right host
response = client.newRequest("127.0.0.1", port)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testProxyBlackList() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new EmptyHttpServlet());
int port = serverConnector.getLocalPort();
proxyServlet.getBlackListHosts().add("localhost:" + port);
// Try with the wrong host
ContentResponse response = client.newRequest("localhost", port)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(403, response.getStatus());
// Try again with the right host
response = client.newRequest("127.0.0.1", port)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testClientExcludedHosts() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{ {
private static final long serialVersionUID = 1L;
@Override @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{ {
FileInputStream fis = new FileInputStream(file); if (req.getHeader("Via") != null)
ServletOutputStream output = response.getOutputStream(); resp.addHeader(PROXIED_HEADER, "true");
byte[] buffer = new byte[1024]; }
int read; });
while ((read = fis.read(buffer)) >= 0) int port = serverConnector.getLocalPort();
output.write(buffer, 0, read); client.getProxyConfiguration().getExcludedHosts().add("127.0.0.1:" + port);
fis.close();
// Try with a proxied host
ContentResponse response = client.newRequest("localhost", port)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
// Try again with an excluded host
response = client.newRequest("127.0.0.1", port)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Test
public void testTransparentProxy() throws Exception
{
final String target = "/test";
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
resp.setStatus(target.equals(req.getRequestURI()) ? 200 : 404);
} }
}); });
String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test"; String proxyTo = "http://localhost:" + serverConnector.getLocalPort();
ContentExchange exchange = new ContentExchange(true) String prefix = "/proxy";
ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix);
prepareProxy(proxyServlet);
// Make the request to the proxy, it should transparently forward to the server
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
.path(prefix + target)
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
@Test
public void testCachingProxy() throws Exception
{
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF};
prepareServer(new HttpServlet()
{ {
@Override @Override
protected void onResponseContent(Buffer content) throws IOException protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{ {
try if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
resp.getOutputStream().write(content);
}
});
// Don't do this at home: this example is not concurrent, not complete,
// it is only used for this test and to verify that ProxyServlet can be
// subclassed enough to write your own caching servlet
final String cacheHeader = "X-Cached";
ProxyServlet proxyServlet = new ProxyServlet()
{
private Map<String, ContentResponse> cache = new HashMap<>();
private Map<String, ByteArrayOutputStream> temp = new HashMap<>();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
ContentResponse cachedResponse = cache.get(request.getRequestURI());
if (cachedResponse != null)
{ {
// Slow down the reader response.setStatus(cachedResponse.getStatus());
TimeUnit.MILLISECONDS.sleep(10); // Should copy headers too, but keep it simple
super.onResponseContent(content); response.addHeader(cacheHeader, "true");
response.getOutputStream().write(cachedResponse.getContent());
} }
catch (InterruptedException x) else
{ {
throw (IOException)new IOException().initCause(x); super.service(request, response);
} }
} }
@Override
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException
{
// Accumulate the response content
ByteArrayOutputStream baos = temp.get(request.getRequestURI());
if (baos == null)
{
baos = new ByteArrayOutputStream();
temp.put(request.getRequestURI(), baos);
}
baos.write(buffer, offset, length);
super.onResponseContent(request, response, proxyResponse, buffer, offset, length);
}
@Override
protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
{
byte[] content = temp.remove(request.getRequestURI()).toByteArray();
ContentResponse cached = new HttpContentResponse(proxyResponse, content, null);
cache.put(request.getRequestURI(), cached);
super.onResponseSuccess(request, response, proxyResponse);
}
}; };
exchange.setURL(url); prepareProxy(proxyServlet);
long start = System.nanoTime();
_client.send(exchange); // First request
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone()); ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
long elapsed = System.nanoTime() - start; .send()
Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus()); .get(5, TimeUnit.SECONDS);
Assert.assertEquals(file.length(), exchange.getResponseContentBytes().length); Assert.assertEquals(200, response.getStatus());
long millis = TimeUnit.NANOSECONDS.toMillis(elapsed); Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
long rate = file.length() / 1024 * 1000 / millis; Assert.assertArrayEquals(content, response.getContent());
System.out.printf("download rate = %d KiB/s%n", rate);
// Second request should be cached
response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(cacheHeader));
Assert.assertArrayEquals(content, response.getContent());
}
@Test
public void testRedirectsAreProxied() throws Exception
{
prepareProxy(new ProxyServlet());
prepareServer(new HttpServlet()
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
resp.sendRedirect("/");
}
});
client.setFollowRedirects(false);
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertEquals(302, response.getStatus());
Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER));
}
// TODO: test proxy authentication
private static class EmptyHttpServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
}
} }
} }

View File

@ -0,0 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.proxy.LEVEL=DEBUG

Binary file not shown.

Binary file not shown.

View File

@ -25,12 +25,13 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Connection;
@ -514,6 +515,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
} }
} }
public ByteBuffer getRequestBuffer()
{
return _requestBuffer;
}
private class Input extends ByteBufferHttpInput private class Input extends ByteBufferHttpInput
{ {
@ -642,24 +647,32 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
switch (version) switch (version)
{ {
case HTTP_0_9: case HTTP_0_9:
{
persistent = false; persistent = false;
break; break;
}
case HTTP_1_0: case HTTP_1_0:
{
persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
if (!persistent)
persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
if (persistent) if (persistent)
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE); getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
break; break;
}
case HTTP_1_1: case HTTP_1_1:
{
persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
if (!persistent)
persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
if (!persistent) if (!persistent)
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
break; break;
}
default: default:
{
throw new IllegalStateException(); throw new IllegalStateException();
}
} }
if (!persistent) if (!persistent)

View File

@ -20,10 +20,9 @@ package org.eclipse.jetty.util;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.nio.charset.Charset;
/* ------------------------------------------------------------ */
/** Fast B64 Encoder/Decoder as described in RFC 1421. /** Fast B64 Encoder/Decoder as described in RFC 1421.
* <p>Does not insert or interpret whitespace as described in RFC * <p>Does not insert or interpret whitespace as described in RFC
* 1521. If you require this you must pre/post process your data. * 1521. If you require this you must pre/post process your data.
@ -33,9 +32,8 @@ import java.io.UnsupportedEncodingException;
*/ */
public class B64Code public class B64Code
{ {
// ------------------------------------------------------------------ private static final char __pad='=';
static final char __pad='='; private static final char[] __rfc1421alphabet=
static final char[] __rfc1421alphabet=
{ {
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
@ -43,8 +41,7 @@ public class B64Code
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
}; };
static final byte[] __rfc1421nibbles; private static final byte[] __rfc1421nibbles;
static static
{ {
__rfc1421nibbles=new byte[256]; __rfc1421nibbles=new byte[256];
@ -55,26 +52,21 @@ public class B64Code
__rfc1421nibbles[(byte)__pad]=0; __rfc1421nibbles[(byte)__pad]=0;
} }
// ------------------------------------------------------------------ private B64Code()
{
}
/** /**
* Base 64 encode as described in RFC 1421. * Base 64 encode as described in RFC 1421.
* <p>Does not insert whitespace as described in RFC 1521. * <p>Does not insert whitespace as described in RFC 1521.
* @param s String to encode. * @param s String to encode.
* @return String containing the encoded form of the input. * @return String containing the encoded form of the input.
*/ */
static public String encode(String s) public static String encode(String s)
{ {
try return encode(s,null);
{
return encode(s,null);
}
catch (UnsupportedEncodingException e)
{
throw new IllegalArgumentException(e.toString());
}
} }
// ------------------------------------------------------------------
/** /**
* Base 64 encode as described in RFC 1421. * Base 64 encode as described in RFC 1421.
* <p>Does not insert whitespace as described in RFC 1521. * <p>Does not insert whitespace as described in RFC 1521.
@ -83,19 +75,16 @@ public class B64Code
* the character encoding of the provided input String. * the character encoding of the provided input String.
* @return String containing the encoded form of the input. * @return String containing the encoded form of the input.
*/ */
static public String encode(String s,String charEncoding) public static String encode(String s,String charEncoding)
throws UnsupportedEncodingException
{ {
byte[] bytes; byte[] bytes;
if (charEncoding==null) if (charEncoding==null)
bytes=s.getBytes(StringUtil.__ISO_8859_1); bytes=s.getBytes(Charset.forName(StringUtil.__ISO_8859_1));
else else
bytes=s.getBytes(charEncoding); bytes=s.getBytes(Charset.forName(charEncoding));
return new String(encode(bytes)); return new String(encode(bytes));
} }
// ------------------------------------------------------------------
/** /**
* Fast Base 64 encode as described in RFC 1421. * Fast Base 64 encode as described in RFC 1421.
* <p>Does not insert whitespace as described in RFC 1521. * <p>Does not insert whitespace as described in RFC 1521.
@ -103,7 +92,7 @@ public class B64Code
* @param b byte array to encode. * @param b byte array to encode.
* @return char array containing the encoded form of the input. * @return char array containing the encoded form of the input.
*/ */
static public char[] encode(byte[] b) public static char[] encode(byte[] b)
{ {
if (b==null) if (b==null)
return null; return null;
@ -123,7 +112,7 @@ public class B64Code
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03]; c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
c[ci++]=__rfc1421alphabet[b2&077]; c[ci++]=__rfc1421alphabet[b2&0x3f];
} }
if (bLen!=bi) if (bLen!=bi)
@ -155,7 +144,6 @@ public class B64Code
return c; return c;
} }
// ------------------------------------------------------------------
/** /**
* Fast Base 64 encode as described in RFC 1421 and RFC2045 * Fast Base 64 encode as described in RFC 1421 and RFC2045
* <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true. * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
@ -164,7 +152,7 @@ public class B64Code
* @param rfc2045 If true, break lines at 76 characters with CRLF * @param rfc2045 If true, break lines at 76 characters with CRLF
* @return char array containing the encoded form of the input. * @return char array containing the encoded form of the input.
*/ */
static public char[] encode(byte[] b, boolean rfc2045) public static char[] encode(byte[] b, boolean rfc2045)
{ {
if (b==null) if (b==null)
return null; return null;
@ -188,7 +176,7 @@ public class B64Code
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f]; c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f]; c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03]; c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
c[ci++]=__rfc1421alphabet[b2&077]; c[ci++]=__rfc1421alphabet[b2&0x3f];
l+=4; l+=4;
if (l%76==0) if (l%76==0)
{ {
@ -228,7 +216,6 @@ public class B64Code
return c; return c;
} }
// ------------------------------------------------------------------
/** /**
* Base 64 decode as described in RFC 2045. * Base 64 decode as described in RFC 2045.
* <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
@ -236,20 +223,18 @@ public class B64Code
* @param charEncoding String representing the character encoding * @param charEncoding String representing the character encoding
* used to map the decoded bytes into a String. * used to map the decoded bytes into a String.
* @return String decoded byte array. * @return String decoded byte array.
* @throws UnsupportedEncodingException if the encoding is not supported * @throws UnsupportedCharsetException if the encoding is not supported
* @throws IllegalArgumentException if the input is not a valid * @throws IllegalArgumentException if the input is not a valid
* B64 encoding. * B64 encoding.
*/ */
static public String decode(String encoded,String charEncoding) public static String decode(String encoded,String charEncoding)
throws UnsupportedEncodingException
{ {
byte[] decoded=decode(encoded); byte[] decoded=decode(encoded);
if (charEncoding==null) if (charEncoding==null)
return new String(decoded); return new String(decoded);
return new String(decoded,charEncoding); return new String(decoded,Charset.forName(charEncoding));
} }
/* ------------------------------------------------------------ */
/** /**
* Fast Base 64 decode as described in RFC 1421. * Fast Base 64 decode as described in RFC 1421.
* *
@ -262,7 +247,7 @@ public class B64Code
* @throws IllegalArgumentException if the input is not a valid * @throws IllegalArgumentException if the input is not a valid
* B64 encoding. * B64 encoding.
*/ */
static public byte[] decode(char[] b) public static byte[] decode(char[] b)
{ {
if (b==null) if (b==null)
return null; return null;
@ -337,7 +322,6 @@ public class B64Code
return r; return r;
} }
/* ------------------------------------------------------------ */
/** /**
* Base 64 decode as described in RFC 2045. * Base 64 decode as described in RFC 2045.
* <p>Unlike {@link #decode(char[])}, extra whitespace is ignored. * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
@ -346,7 +330,7 @@ public class B64Code
* @throws IllegalArgumentException if the input is not a valid * @throws IllegalArgumentException if the input is not a valid
* B64 encoding. * B64 encoding.
*/ */
static public byte[] decode(String encoded) public static byte[] decode(String encoded)
{ {
if (encoded==null) if (encoded==null)
return null; return null;
@ -393,7 +377,6 @@ public class B64Code
return bout.toByteArray(); return bout.toByteArray();
} }
/* ------------------------------------------------------------ */
public static void encode(int value,Appendable buf) throws IOException public static void encode(int value,Appendable buf) throws IOException
{ {
buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]); buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
@ -405,7 +388,6 @@ public class B64Code
buf.append('='); buf.append('=');
} }
/* ------------------------------------------------------------ */
public static void encode(long lvalue,Appendable buf) throws IOException public static void encode(long lvalue,Appendable buf) throws IOException
{ {
int value=(int)(0xFFFFFFFC&(lvalue>>32)); int value=(int)(0xFFFFFFFC&(lvalue>>32));

View File

@ -413,6 +413,7 @@
<module>examples/async-rest</module> <module>examples/async-rest</module>
<module>jetty-rewrite</module> <module>jetty-rewrite</module>
<module>jetty-nosql</module> <module>jetty-nosql</module>
<module>jetty-proxy</module>
<module>tests</module> <module>tests</module>
<!--module>dists</module--> <!--module>dists</module-->
@ -421,7 +422,6 @@
<module>jetty-runner</module> <module>jetty-runner</module>
<module>jetty-rhttp</module> <module>jetty-rhttp</module>
<module>jetty-proxy</module>
<module>jetty-monitor</module> <module>jetty-monitor</module>
<module>jetty-nested</module> <module>jetty-nested</module>
<module>jetty-overlay-deployer</module> <module>jetty-overlay-deployer</module>
@ -578,7 +578,9 @@
<profile> <profile>
<id>release</id> <id>release</id>
<modules> <modules>
<module>aggregates/jetty-all</module> <!--
<module>jetty-aggregate</module>
-->
</modules> </modules>
</profile> </profile>
<profile> <profile>