Merge from 'master' to 'ws-refactor'
This commit is contained in:
commit
6f0d9e546c
|
@ -0,0 +1,175 @@
|
|||
<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">
|
||||
<parent>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<version>9.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.eclipse.jetty.aggregate</groupId>
|
||||
<artifactId>jetty-all</artifactId>
|
||||
<name>Jetty :: Aggregate :: All core Jetty</name>
|
||||
<build>
|
||||
<sourceDirectory>${project.build.directory}/sources</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>unpack-dependencies</id>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<excludes>**/MANIFEST.MF,javax/**</excludes>
|
||||
<excludeArtifactIds>javax</excludeArtifactIds>
|
||||
<excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
|
||||
<outputDirectory>${project.build.directory}/classes</outputDirectory>
|
||||
<overWriteReleases>false</overWriteReleases>
|
||||
<overWriteSnapshots>true</overWriteSnapshots>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>unpack-source</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>unpack-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<classifier>sources</classifier>
|
||||
<includes>**/*</includes>
|
||||
<excludes>META-INF/**,**/Servlet3Continuation*,**/Jetty6Continuation*</excludes>
|
||||
<includeGroupIds>org.eclipse.jetty</includeGroupIds>
|
||||
<excludeArtifactIds>javax</excludeArtifactIds>
|
||||
<excludeGroupIds>javax,org.eclipse.jetty.orbit</excludeGroupIds>
|
||||
<outputDirectory>${project.build.directory}/sources</outputDirectory>
|
||||
<overWriteReleases>true</overWriteReleases>
|
||||
<overWriteSnapshots>true</overWriteSnapshots>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins
|
||||
</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>package</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<mode>development</mode>
|
||||
<url>http://eclipse.org/jetty</url>
|
||||
<Built-By>${user.name}</Built-By>
|
||||
<package>org.eclipse.jetty</package>
|
||||
<Bundle-License>http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/tree/NOTICE.txt</Bundle-License>
|
||||
<Bundle-Name>Jetty</Bundle-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>javadoc-jar</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-deploy</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||
<artifactId>javax.servlet</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jmx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-plus</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-annotations</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jaspi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-jndi</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-rewrite</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlets</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -20,7 +20,6 @@ package org.eclipse.jetty.client;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketException;
|
||||
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.CookieStore;
|
||||
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.Response;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -127,6 +127,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
private volatile long idleTimeout;
|
||||
private volatile boolean tcpNoDelay = true;
|
||||
private volatile boolean dispatchIO = true;
|
||||
private volatile ProxyConfiguration proxyConfig;
|
||||
|
||||
public HttpClient()
|
||||
{
|
||||
|
@ -351,7 +352,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
channel.bind(bindAddress);
|
||||
configure(channel);
|
||||
channel.configureBlocking(false);
|
||||
channel.connect(new InetSocketAddress(destination.getHost(), destination.getPort()));
|
||||
channel.connect(destination.getConnectAddress());
|
||||
|
||||
Future<Connection> result = new ConnectionCallback(destination, callback);
|
||||
selectorManager.connect(channel, result);
|
||||
|
@ -596,6 +597,16 @@ public class HttpClient extends ContainerLifeCycle
|
|||
this.dispatchIO = dispatchIO;
|
||||
}
|
||||
|
||||
public ProxyConfiguration getProxyConfiguration()
|
||||
{
|
||||
return proxyConfig;
|
||||
}
|
||||
|
||||
public void setProxyConfiguration(ProxyConfiguration proxyConfig)
|
||||
{
|
||||
this.proxyConfig = proxyConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
|
|
|
@ -155,6 +155,11 @@ public class HttpConnection extends AbstractConnection implements Connection
|
|||
path = "/";
|
||||
request.path(path);
|
||||
}
|
||||
if (destination.isProxied() && HttpMethod.CONNECT != request.getMethod())
|
||||
{
|
||||
path = request.getURI();
|
||||
request.path(path);
|
||||
}
|
||||
|
||||
Fields fields = request.getParams();
|
||||
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
|
||||
// because if the response has already successfully
|
||||
// arrived then we failed to abort the exchange
|
||||
sender.abort(exchange, reason);
|
||||
return receiver.abort(exchange, reason);
|
||||
sender.abort(exchange, cause);
|
||||
return receiver.abort(exchange, cause);
|
||||
}
|
||||
|
||||
public void proceed(boolean proceed)
|
||||
|
|
|
@ -77,9 +77,9 @@ public class HttpContentResponse implements ContentResponse
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(String reason)
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
return response.abort(reason);
|
||||
return response.abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -98,10 +98,10 @@ public class HttpConversation implements Attributes
|
|||
attributes.clear();
|
||||
}
|
||||
|
||||
public boolean abort(String reason)
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
HttpExchange exchange = exchanges.peekLast();
|
||||
return exchange != null && exchange.abort(reason);
|
||||
return exchange != null && exchange.abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -41,23 +41,27 @@ public class HttpCookieStore implements CookieStore
|
|||
|
||||
String host = destination.getHost();
|
||||
int port = destination.getPort();
|
||||
String key = host + ":" + port + path;
|
||||
String key = host + ":" + port;
|
||||
|
||||
// First lookup: direct hit
|
||||
Queue<HttpCookie> cookies = allCookies.get(key);
|
||||
// Root path lookup
|
||||
Queue<HttpCookie> cookies = allCookies.get(key + "/");
|
||||
if (cookies != null)
|
||||
accumulateCookies(destination, cookies, result);
|
||||
|
||||
// Second lookup: root path
|
||||
if (!"/".equals(path))
|
||||
// Path lookup
|
||||
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);
|
||||
if (cookies != null)
|
||||
accumulateCookies(destination, cookies, result);
|
||||
if (segment.length() > 0)
|
||||
key += "/";
|
||||
}
|
||||
|
||||
// Third lookup: parent domains
|
||||
// Domain lookup
|
||||
int domains = host.split("\\.").length - 1;
|
||||
for (int i = 2; i <= domains; ++i)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -27,13 +28,19 @@ import java.util.concurrent.ArrayBlockingQueue;
|
|||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
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.Response;
|
||||
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.FutureCallback;
|
||||
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 HttpClient client;
|
||||
private final String scheme;
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final InetSocketAddress address;
|
||||
private final Queue<RequestContext> requests;
|
||||
private final BlockingQueue<Connection> idleConnections;
|
||||
private final BlockingQueue<Connection> activeConnections;
|
||||
private final RequestNotifier requestNotifier;
|
||||
private final ResponseNotifier responseNotifier;
|
||||
private final InetSocketAddress proxyAddress;
|
||||
|
||||
public HttpDestination(HttpClient client, String scheme, String host, int port)
|
||||
{
|
||||
this.client = client;
|
||||
this.scheme = scheme;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.address = new InetSocketAddress(host, port);
|
||||
this.requests = new ArrayBlockingQueue<>(client.getMaxQueueSizePerAddress());
|
||||
this.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
|
||||
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
|
||||
this.requestNotifier = new RequestNotifier(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()
|
||||
|
@ -88,23 +98,33 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
@Override
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
return address.getHostString();
|
||||
}
|
||||
|
||||
@Override
|
||||
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)
|
||||
{
|
||||
if (!scheme.equals(request.getScheme()))
|
||||
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);
|
||||
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);
|
||||
|
||||
RequestContext requestContext = new RequestContext(request, listeners);
|
||||
|
@ -139,7 +159,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
public Future<Connection> newConnection()
|
||||
{
|
||||
FutureCallback<Connection> result = new FutureCallback<>();
|
||||
newConnection(result);
|
||||
newConnection(new CONNECTCallback(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -170,30 +190,31 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
if (connectionCount.compareAndSet(current, next))
|
||||
{
|
||||
LOG.debug("Creating connection {}/{} for {}", next, maxConnections, this);
|
||||
newConnection(new Callback<Connection>()
|
||||
|
||||
CONNECTCallback connectCallback = new CONNECTCallback(new Callback<Connection>()
|
||||
{
|
||||
@Override
|
||||
public void completed(Connection connection)
|
||||
{
|
||||
LOG.debug("Created connection {}/{} {} for {}", next, maxConnections, connection, HttpDestination.this);
|
||||
process(connection, true);
|
||||
}
|
||||
|
||||
@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()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
drain(x);
|
||||
if (connection != null)
|
||||
connection.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
newConnection(new TCPCallback(next, maxConnections, connectCallback));
|
||||
// Try again the idle connections
|
||||
return idleConnections.poll();
|
||||
}
|
||||
|
@ -248,9 +269,10 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
{
|
||||
final Request request = requestContext.request;
|
||||
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);
|
||||
}
|
||||
else
|
||||
|
@ -300,10 +322,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
|
||||
public void remove(Connection connection)
|
||||
{
|
||||
LOG.debug("{} removed", connection);
|
||||
connectionCount.decrementAndGet();
|
||||
activeConnections.remove(connection);
|
||||
idleConnections.remove(connection);
|
||||
boolean removed = activeConnections.remove(connection);
|
||||
removed |= idleConnections.remove(connection);
|
||||
if (removed)
|
||||
{
|
||||
LOG.debug("{} removed", connection);
|
||||
connectionCount.decrementAndGet();
|
||||
}
|
||||
|
||||
// We need to execute queued requests even if this connection failed.
|
||||
// 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);
|
||||
}
|
||||
|
||||
public boolean abort(Request request, String reason)
|
||||
public boolean abort(Request request, Throwable cause)
|
||||
{
|
||||
for (RequestContext requestContext : requests)
|
||||
{
|
||||
|
@ -343,7 +368,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
if (requests.remove(requestContext))
|
||||
{
|
||||
// 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);
|
||||
return true;
|
||||
}
|
||||
|
@ -352,13 +377,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
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);
|
||||
HttpResponseException responseFailure = new HttpResponseException(reason, response);
|
||||
responseNotifier.notifyFailure(listeners, response, responseFailure);
|
||||
HttpRequestException requestFailure = new HttpRequestException(reason, request);
|
||||
responseNotifier.notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure));
|
||||
responseNotifier.notifyFailure(listeners, response, cause);
|
||||
responseNotifier.notifyComplete(listeners, new Result(request, cause, response, cause));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -382,7 +405,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
@Override
|
||||
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
|
||||
|
@ -396,4 +424,90 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
|
|||
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));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,10 +193,10 @@ public class HttpExchange
|
|||
return new AtomicMarkableReference<>(result, modified);
|
||||
}
|
||||
|
||||
public boolean abort(String reason)
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
LOG.debug("Aborting {} reason {}", this, reason);
|
||||
boolean aborted = connection.abort(this, reason);
|
||||
LOG.debug("Aborting {} reason {}", this, cause);
|
||||
boolean aborted = connection.abort(this, cause);
|
||||
LOG.debug("Aborted {}: {}", this, aborted);
|
||||
return aborted;
|
||||
}
|
||||
|
|
|
@ -387,9 +387,9 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
|
|||
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)
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
@ -63,7 +64,7 @@ public class HttpRequest implements Request
|
|||
private long idleTimeout;
|
||||
private ContentProvider content;
|
||||
private boolean followRedirects;
|
||||
private volatile boolean aborted;
|
||||
private volatile Throwable aborted;
|
||||
|
||||
public HttpRequest(HttpClient client, URI uri)
|
||||
{
|
||||
|
@ -403,17 +404,17 @@ public class HttpRequest implements Request
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(String reason)
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
aborted = true;
|
||||
if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, reason))
|
||||
aborted = Objects.requireNonNull(cause);
|
||||
if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, cause))
|
||||
return true;
|
||||
HttpConversation conversation = client.getConversation(getConversationID(), false);
|
||||
return conversation != null && conversation.abort(reason);
|
||||
return conversation != null && conversation.abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAborted()
|
||||
public Throwable getAbortCause()
|
||||
{
|
||||
return aborted;
|
||||
}
|
||||
|
|
|
@ -98,9 +98,9 @@ public class HttpResponse implements Response
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(String reason)
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
return request.abort(reason);
|
||||
return request.abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -82,9 +82,10 @@ public class HttpSender
|
|||
}
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (request.isAborted())
|
||||
Throwable cause = request.getAbortCause();
|
||||
if (cause != null)
|
||||
{
|
||||
exchange.abort(null);
|
||||
exchange.abort(cause);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -400,7 +401,7 @@ public class HttpSender
|
|||
|
||||
Result result = completion.getReference();
|
||||
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();
|
||||
exchange.terminateResponse();
|
||||
|
@ -418,12 +419,12 @@ public class HttpSender
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean abort(HttpExchange exchange, String reason)
|
||||
public boolean abort(HttpExchange exchange, Throwable cause)
|
||||
{
|
||||
State current = state.get();
|
||||
boolean abortable = current == State.IDLE || current == State.SEND ||
|
||||
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)
|
||||
|
|
|
@ -126,8 +126,9 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
|
|||
@Override
|
||||
public void onBegin(Request redirect)
|
||||
{
|
||||
if (request.isAborted())
|
||||
redirect.abort(null);
|
||||
Throwable cause = request.getAbortCause();
|
||||
if (cause != null)
|
||||
redirect.abort(cause);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** 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 class ProxyConfiguration
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -308,15 +308,16 @@ public interface 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
|
||||
*/
|
||||
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
|
||||
{
|
||||
|
|
|
@ -71,10 +71,10 @@ public interface 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
|
||||
*/
|
||||
boolean abort(String reason);
|
||||
boolean abort(Throwable cause);
|
||||
|
||||
public interface ResponseListener extends EventListener
|
||||
{
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
|
||||
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.ContentResponse;
|
||||
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)
|
||||
{
|
||||
String encoding = StringUtil.__ISO_8859_1;
|
||||
try
|
||||
{
|
||||
String value = "Basic " + B64Code.encode(user + ":" + password, encoding);
|
||||
return new BasicResult(request.getURI(), value);
|
||||
}
|
||||
catch (UnsupportedEncodingException x)
|
||||
{
|
||||
throw new UnsupportedCharsetException(encoding);
|
||||
}
|
||||
String value = "Basic " + B64Code.encode(user + ":" + password, encoding);
|
||||
return new BasicResult(request.getURI(), value);
|
||||
}
|
||||
|
||||
private static class BasicResult implements Result
|
||||
|
|
|
@ -55,7 +55,7 @@ public class BlockingResponseListener extends BufferingResponseListener implemen
|
|||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
cancelled = true;
|
||||
return request.abort("Cancelled");
|
||||
return request.abort(new CancellationException());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,8 +83,9 @@ public class BlockingResponseListener extends BufferingResponseListener implemen
|
|||
boolean expired = !latch.await(timeout, unit);
|
||||
if (expired)
|
||||
{
|
||||
request.abort("Total timeout elapsed");
|
||||
throw new TimeoutException();
|
||||
TimeoutException reason = new TimeoutException();
|
||||
request.abort(reason);
|
||||
throw reason;
|
||||
}
|
||||
return getResult();
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ public abstract class BufferingResponseListener extends Response.Listener.Empty
|
|||
HttpFields headers = response.getHeaders();
|
||||
long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||
if (length > maxLength)
|
||||
response.abort("Buffering capacity exceeded");
|
||||
response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
|
||||
|
||||
String contentType = headers.get(HttpHeader.CONTENT_TYPE);
|
||||
if (contentType != null)
|
||||
|
|
|
@ -49,6 +49,13 @@ public class InputStreamContentProvider implements ContentProvider
|
|||
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
|
||||
public Iterator<ByteBuffer> iterator()
|
||||
{
|
||||
|
@ -71,18 +78,18 @@ public class InputStreamContentProvider implements ContentProvider
|
|||
int read = stream.read(buffer);
|
||||
if (read > 0)
|
||||
{
|
||||
return ByteBuffer.wrap(buffer, 0, read);
|
||||
return onRead(buffer, 0, read);
|
||||
}
|
||||
else if (read < 0)
|
||||
{
|
||||
if (eof)
|
||||
throw new NoSuchElementException();
|
||||
eof = true;
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
return onRead(buffer, 0, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BufferUtil.EMPTY_BUFFER;
|
||||
return onRead(buffer, 0, 0);
|
||||
}
|
||||
}
|
||||
catch (IOException x)
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.eclipse.jetty.client.util;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.client.Schedulable;
|
||||
|
@ -38,14 +39,14 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
|
|||
private final long timeout;
|
||||
private final TimeUnit unit;
|
||||
private final Request request;
|
||||
private final Response.Listener delegate;
|
||||
private final Response.CompleteListener delegate;
|
||||
|
||||
public TimedResponseListener(long timeout, TimeUnit unit, Request request)
|
||||
{
|
||||
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.unit = unit;
|
||||
|
@ -56,31 +57,36 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
|
|||
@Override
|
||||
public void onBegin(Response response)
|
||||
{
|
||||
delegate.onBegin(response);
|
||||
if (delegate instanceof Response.BeginListener)
|
||||
((Response.BeginListener)delegate).onBegin(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Response response)
|
||||
{
|
||||
delegate.onHeaders(response);
|
||||
if (delegate instanceof Response.HeadersListener)
|
||||
((Response.HeadersListener)delegate).onHeaders(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContent(Response response, ByteBuffer content)
|
||||
{
|
||||
delegate.onContent(response, content);
|
||||
if (delegate instanceof Response.ContentListener)
|
||||
((Response.ContentListener)delegate).onContent(response, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
delegate.onSuccess(response);
|
||||
if (delegate instanceof Response.SuccessListener)
|
||||
((Response.SuccessListener)delegate).onSuccess(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Response response, Throwable failure)
|
||||
{
|
||||
delegate.onFailure(response, failure);
|
||||
if (delegate instanceof Response.FailureListener)
|
||||
((Response.FailureListener)delegate).onFailure(response, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -111,7 +117,7 @@ public class TimedResponseListener implements Response.Listener, Schedulable, Ru
|
|||
@Override
|
||||
public void run()
|
||||
{
|
||||
request.abort("Total timeout elapsed");
|
||||
request.abort(new TimeoutException("Total timeout elapsed"));
|
||||
}
|
||||
|
||||
public boolean cancel()
|
||||
|
|
|
@ -408,7 +408,7 @@ public class HttpClientContinueTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onBegin(Response response)
|
||||
{
|
||||
response.abort(null);
|
||||
response.abort(new Exception());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -151,7 +151,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
|||
|
||||
TimeUnit.MILLISECONDS.sleep(2 * timeout);
|
||||
|
||||
Assert.assertFalse(request.isAborted());
|
||||
Assert.assertNull(request.getAbortCause());
|
||||
}
|
||||
|
||||
@Slow
|
||||
|
@ -208,7 +208,7 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
|||
|
||||
TimeUnit.MILLISECONDS.sleep(2 * timeout);
|
||||
|
||||
Assert.assertFalse(request.isAborted());
|
||||
Assert.assertNull(request.getAbortCause());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,6 +100,21 @@ public class HttpCookieStoreTest
|
|||
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
|
||||
public void testCookieStoredWithParentDomainIsRetrievedWithChildDomain() throws Exception
|
||||
{
|
||||
|
@ -118,6 +133,19 @@ public class HttpCookieStoreTest
|
|||
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
|
||||
public void testExpiredCookieIsNotRetrieved() throws Exception
|
||||
{
|
||||
|
|
|
@ -33,7 +33,6 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
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.util.ByteBufferContentProvider;
|
||||
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.log.StdErrLog;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -58,6 +56,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
start(new EmptyServerHandler());
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final AtomicBoolean begin = new AtomicBoolean();
|
||||
try
|
||||
{
|
||||
|
@ -68,7 +67,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onQueued(Request request)
|
||||
{
|
||||
request.abort(null);
|
||||
request.abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,7 +81,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
catch (ExecutionException x)
|
||||
{
|
||||
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class));
|
||||
Assert.assertSame(cause, x.getCause());
|
||||
Assert.assertFalse(begin.get());
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +92,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
{
|
||||
start(new EmptyServerHandler());
|
||||
|
||||
final Throwable cause = new Exception();
|
||||
final CountDownLatch aborted = new CountDownLatch(1);
|
||||
final CountDownLatch headers = new CountDownLatch(1);
|
||||
try
|
||||
|
@ -104,7 +104,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onBegin(Request request)
|
||||
{
|
||||
if (request.abort(null))
|
||||
if (request.abort(cause))
|
||||
aborted.countDown();
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
catch (ExecutionException x)
|
||||
{
|
||||
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class));
|
||||
Assert.assertSame(cause, x.getCause());
|
||||
Assert.assertTrue(aborted.await(5, 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
|
||||
// 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);
|
||||
try
|
||||
{
|
||||
|
@ -144,7 +145,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onHeaders(Request request)
|
||||
{
|
||||
if (request.abort(null))
|
||||
if (request.abort(cause))
|
||||
aborted.countDown();
|
||||
}
|
||||
})
|
||||
|
@ -154,7 +155,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
catch (ExecutionException x)
|
||||
{
|
||||
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class));
|
||||
Assert.assertSame(cause, x.getCause());
|
||||
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);
|
||||
final Throwable cause = new Exception();
|
||||
try
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
|
@ -196,7 +193,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onHeaders(Request request)
|
||||
{
|
||||
request.abort(null);
|
||||
request.abort(cause);
|
||||
}
|
||||
})
|
||||
.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)
|
||||
{
|
||||
Throwable cause = x.getCause();
|
||||
if (cause instanceof EOFException)
|
||||
Throwable abort = x.getCause();
|
||||
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
|
||||
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);
|
||||
// Expected
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -270,7 +258,8 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
|
||||
TimeUnit.MILLISECONDS.sleep(delay);
|
||||
|
||||
request.abort(null);
|
||||
Throwable cause = new Exception();
|
||||
request.abort(cause);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -278,7 +267,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
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().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
|
||||
if (!result.isFailed())
|
||||
result.getRequest().abort(null);
|
||||
result.getRequest().abort(cause);
|
||||
super.onComplete(result);
|
||||
}
|
||||
});
|
||||
|
@ -320,7 +310,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
|
|||
}
|
||||
catch (ExecutionException x)
|
||||
{
|
||||
Assert.assertThat(x.getCause(), Matchers.instanceOf(HttpResponseException.class));
|
||||
Assert.assertSame(cause, x.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onBegin(Response response)
|
||||
{
|
||||
response.abort(null);
|
||||
response.abort(new Exception());
|
||||
}
|
||||
})
|
||||
.send(new Response.CompleteListener()
|
||||
|
@ -88,7 +88,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onHeaders(Response response)
|
||||
{
|
||||
response.abort(null);
|
||||
response.abort(new Exception());
|
||||
}
|
||||
})
|
||||
.send(new Response.CompleteListener()
|
||||
|
@ -136,7 +136,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
|
|||
@Override
|
||||
public void onContent(Response response, ByteBuffer content)
|
||||
{
|
||||
response.abort(null);
|
||||
response.abort(new Exception());
|
||||
}
|
||||
})
|
||||
.send(new Response.CompleteListener()
|
||||
|
|
|
@ -191,7 +191,7 @@ public class Usage
|
|||
}
|
||||
else
|
||||
{
|
||||
response.abort(null);
|
||||
response.abort(new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
package org.eclipse.jetty.security.jaspi;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -137,7 +138,7 @@ public class JaspiAuthenticatorFactory extends DefaultAuthenticatorFactory
|
|||
{
|
||||
if (_serviceSubject!=null)
|
||||
return _serviceSubject;
|
||||
List subjects = server.getBeans(Subject.class);
|
||||
List<Subject> subjects = (List<Subject>)server.getBeans(Subject.class);
|
||||
if (subjects.size()>0)
|
||||
return (Subject)subjects.get(0);
|
||||
return null;
|
||||
|
|
|
@ -31,7 +31,7 @@ import javax.security.auth.message.MessagePolicy;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeaders;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -75,7 +75,7 @@ public class BasicAuthModule extends BaseAuthModule
|
|||
{
|
||||
HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
|
||||
HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
|
||||
String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -87,7 +87,7 @@ public class BasicAuthModule extends BaseAuthModule
|
|||
}
|
||||
|
||||
if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; }
|
||||
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "basic realm=\"" + realmName + '"');
|
||||
response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "basic realm=\"" + realmName + '"');
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return AuthStatus.SEND_CONTINUE;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ import javax.security.auth.message.MessagePolicy;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeaders;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.util.security.Credential;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
|
@ -87,7 +87,7 @@ public class DigestAuthModule extends BaseAuthModule
|
|||
{
|
||||
HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
|
||||
HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
|
||||
String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
String credentials = request.getHeader(HttpHeader.AUTHORIZATION.asString());
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -155,7 +155,7 @@ public class DigestAuthModule extends BaseAuthModule
|
|||
if (!isMandatory(messageInfo)) { return AuthStatus.SUCCESS; }
|
||||
String domain = request.getContextPath();
|
||||
if (domain == null) domain = "/";
|
||||
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + realmName
|
||||
response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(), "Digest realm=\"" + realmName
|
||||
+ "\", domain=\""
|
||||
+ domain
|
||||
+ "\", nonce=\""
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<?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">
|
||||
<parent>
|
||||
<artifactId>jetty-project</artifactId>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<version>9.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>jetty-proxy</artifactId>
|
||||
<name>Jetty :: Proxy</name>
|
||||
<description>Jetty Proxy</description>
|
||||
<properties>
|
||||
<bundle-symbolic-name>${project.groupId}.servlets</bundle-symbolic-name>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>manifest</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<instructions>
|
||||
<Import-Package>javax.servlet.*;version="2.6.0",*</Import-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!--
|
||||
Required for OSGI
|
||||
-->
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<onlyAnalyze>org.eclipse.jetty.servlets.*</onlyAnalyze>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-continuation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-webapp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-util</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.orbit</groupId>
|
||||
<artifactId>javax.servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,62 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||
|
||||
<!-- =============================================================== -->
|
||||
<!-- Configure the Jetty Server -->
|
||||
<!-- -->
|
||||
<!-- Documentation of this file format can be found at: -->
|
||||
<!-- http://docs.codehaus.org/display/JETTY/jetty.xml -->
|
||||
<!-- -->
|
||||
<!-- =============================================================== -->
|
||||
|
||||
|
||||
<Configure id="Proxy" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Server Thread Pool -->
|
||||
<!-- =========================================================== -->
|
||||
<Set name="ThreadPool">
|
||||
<!-- Default queued blocking threadpool
|
||||
-->
|
||||
<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
|
||||
<Set name="minThreads">10</Set>
|
||||
<Set name="maxThreads">50</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- Set connectors -->
|
||||
<!-- =========================================================== -->
|
||||
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Set name="host"><Property name="jetty.host" /></Set>
|
||||
<Set name="port"><Property name="jetty.port" default="8888"/></Set>
|
||||
<Set name="idleTimeout">300000</Set>
|
||||
<Set name="Acceptors">2</Set>
|
||||
<Set name="statsOn">false</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<Set name="handler">
|
||||
<New id="Servlets" class="org.eclipse.jetty.servlet.ServletHandler">
|
||||
<Call name="addServletWithMapping">
|
||||
<Arg>org.eclipse.jetty.servlets.ProxyServlet</Arg>
|
||||
<Arg>/</Arg>
|
||||
</Call>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
<!-- extra options -->
|
||||
<!-- =========================================================== -->
|
||||
<Set name="stopAtShutdown">true</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">true</Set>
|
||||
<Set name="stopTimeout">1000</Set>
|
||||
|
||||
</Configure>
|
|
@ -1,422 +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.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
||||
/**
|
||||
* 6
|
||||
*/
|
||||
public class BalancerServlet extends ProxyServlet
|
||||
{
|
||||
|
||||
private static final class BalancerMember
|
||||
{
|
||||
|
||||
private String _name;
|
||||
|
||||
private String _proxyTo;
|
||||
|
||||
private HttpURI _backendURI;
|
||||
|
||||
public BalancerMember(String name, String proxyTo)
|
||||
{
|
||||
super();
|
||||
_name = name;
|
||||
_proxyTo = proxyTo;
|
||||
_backendURI = new HttpURI(_proxyTo);
|
||||
}
|
||||
|
||||
public String getProxyTo()
|
||||
{
|
||||
return _proxyTo;
|
||||
}
|
||||
|
||||
public HttpURI getBackendURI()
|
||||
{
|
||||
return _backendURI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "BalancerMember [_name=" + _name + ", _proxyTo=" + _proxyTo + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((_name == null)?0:_name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
BalancerMember other = (BalancerMember)obj;
|
||||
if (_name == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,902 +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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.continuation.Continuation;
|
||||
import org.eclipse.jetty.continuation.ContinuationSupport;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.PathMap;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.util.HostMap;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
||||
/**
|
||||
* Asynchronous Proxy Servlet.
|
||||
*
|
||||
* Forward requests to another server either as a standard web proxy (as defined by RFC2616) or as a transparent proxy.
|
||||
* <p>
|
||||
* This servlet needs the jetty-util and jetty-client classes to be available to the web application.
|
||||
* <p>
|
||||
* To facilitate JMX monitoring, the "HttpClient" and "ThreadPool" are set as context attributes prefixed with the servlet name.
|
||||
* <p>
|
||||
* The following init parameters may be used to configure the servlet:
|
||||
* <ul>
|
||||
* <li>name - Name of Proxy servlet (default: "ProxyServlet"
|
||||
* <li>maxThreads - maximum threads
|
||||
* <li>maxConnections - maximum connections per destination
|
||||
* <li>timeout - the period in ms the client will wait for a response from the proxied server
|
||||
* <li>idleTimeout - the period in ms a connection to proxied server can be idle for before it is closed
|
||||
* <li>requestHeaderSize - the size of the request header buffer (d. 6,144)
|
||||
* <li>requestBufferSize - the size of the request buffer (d. 12,288)
|
||||
* <li>responseHeaderSize - the size of the response header buffer (d. 6,144)
|
||||
* <li>responseBufferSize - the size of the response buffer (d. 32,768)
|
||||
* <li>HostHeader - Force the host header to a particular value
|
||||
* <li>whiteList - comma-separated list of allowed proxy destinations
|
||||
* <li>blackList - comma-separated list of forbidden proxy destinations
|
||||
* </ul>
|
||||
*
|
||||
* @see org.eclipse.jetty.server.handler.ConnectHandler
|
||||
*/
|
||||
public class ProxyServlet implements Servlet
|
||||
{
|
||||
protected Logger _log;
|
||||
protected HttpClient _client;
|
||||
protected String _hostHeader;
|
||||
|
||||
protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
|
||||
{
|
||||
_DontProxyHeaders.add("proxy-connection");
|
||||
_DontProxyHeaders.add("connection");
|
||||
_DontProxyHeaders.add("keep-alive");
|
||||
_DontProxyHeaders.add("transfer-encoding");
|
||||
_DontProxyHeaders.add("te");
|
||||
_DontProxyHeaders.add("trailer");
|
||||
_DontProxyHeaders.add("proxy-authorization");
|
||||
_DontProxyHeaders.add("proxy-authenticate");
|
||||
_DontProxyHeaders.add("upgrade");
|
||||
}
|
||||
|
||||
protected ServletConfig _config;
|
||||
protected ServletContext _context;
|
||||
protected HostMap<PathMap> _white = new HostMap<PathMap>();
|
||||
protected HostMap<PathMap> _black = new HostMap<PathMap>();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
|
||||
*/
|
||||
public void init(ServletConfig config) throws ServletException
|
||||
{
|
||||
_config = config;
|
||||
_context = config.getServletContext();
|
||||
|
||||
_hostHeader = config.getInitParameter("HostHeader");
|
||||
|
||||
try
|
||||
{
|
||||
_log = createLogger(config);
|
||||
|
||||
_client = createHttpClient(config);
|
||||
|
||||
if (_context != null)
|
||||
{
|
||||
_context.setAttribute(config.getServletName() + ".ThreadPool",_client.getThreadPool());
|
||||
_context.setAttribute(config.getServletName() + ".HttpClient",_client);
|
||||
}
|
||||
|
||||
String white = config.getInitParameter("whiteList");
|
||||
if (white != null)
|
||||
{
|
||||
parseList(white,_white);
|
||||
}
|
||||
String black = config.getInitParameter("blackList");
|
||||
if (black != null)
|
||||
{
|
||||
parseList(black,_black);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void destroy()
|
||||
{
|
||||
try
|
||||
{
|
||||
_client.stop();
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
_log.debug(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create and return a logger based on the ServletConfig for use in the
|
||||
* proxy servlet
|
||||
*
|
||||
* @param config
|
||||
* @return Logger
|
||||
*/
|
||||
protected Logger createLogger(ServletConfig config)
|
||||
{
|
||||
return Log.getLogger("org.eclipse.jetty.servlets." + config.getServletName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an HttpClientInstance
|
||||
*
|
||||
* @return HttpClient
|
||||
*/
|
||||
protected HttpClient createHttpClientInstance()
|
||||
{
|
||||
return new HttpClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an HttpClient based on ServletConfig
|
||||
*
|
||||
* By default this implementation will create an instance of the
|
||||
* HttpClient for use by this proxy servlet.
|
||||
*
|
||||
* @param config
|
||||
* @return HttpClient
|
||||
* @throws Exception
|
||||
*/
|
||||
protected HttpClient createHttpClient(ServletConfig config) throws Exception
|
||||
{
|
||||
HttpClient client = createHttpClientInstance();
|
||||
client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
|
||||
|
||||
String t = config.getInitParameter("maxThreads");
|
||||
|
||||
if (t != null)
|
||||
{
|
||||
client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t)));
|
||||
}
|
||||
else
|
||||
{
|
||||
client.setThreadPool(new QueuedThreadPool());
|
||||
}
|
||||
|
||||
((QueuedThreadPool)client.getThreadPool()).setName(config.getServletName());
|
||||
|
||||
t = config.getInitParameter("maxConnections");
|
||||
|
||||
if (t != null)
|
||||
{
|
||||
client.setMaxConnectionsPerAddress(Integer.parseInt(t));
|
||||
}
|
||||
|
||||
t = config.getInitParameter("timeout");
|
||||
|
||||
if ( t != null )
|
||||
{
|
||||
client.setTimeout(Long.parseLong(t));
|
||||
}
|
||||
|
||||
t = config.getInitParameter("idleTimeout");
|
||||
|
||||
if ( t != null )
|
||||
{
|
||||
client.setIdleTimeout(Long.parseLong(t));
|
||||
}
|
||||
|
||||
t = config.getInitParameter("requestHeaderSize");
|
||||
|
||||
if ( t != null )
|
||||
{
|
||||
client.setRequestHeaderSize(Integer.parseInt(t));
|
||||
}
|
||||
|
||||
t = config.getInitParameter("requestBufferSize");
|
||||
|
||||
if ( t != null )
|
||||
{
|
||||
client.setRequestBufferSize(Integer.parseInt(t));
|
||||
}
|
||||
|
||||
t = config.getInitParameter("responseHeaderSize");
|
||||
|
||||
if ( t != null )
|
||||
{
|
||||
client.setResponseHeaderSize(Integer.parseInt(t));
|
||||
}
|
||||
|
||||
t = config.getInitParameter("responseBufferSize");
|
||||
|
||||
if ( t != null )
|
||||
{
|
||||
client.setResponseBufferSize(Integer.parseInt(t));
|
||||
}
|
||||
|
||||
client.start();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Helper function to process a parameter value containing a list of new entries and initialize the specified host map.
|
||||
*
|
||||
* @param list
|
||||
* comma-separated list of new entries
|
||||
* @param hostMap
|
||||
* target host map
|
||||
*/
|
||||
private void parseList(String list, HostMap<PathMap> hostMap)
|
||||
{
|
||||
if (list != null && list.length() > 0)
|
||||
{
|
||||
int idx;
|
||||
String entry;
|
||||
|
||||
StringTokenizer entries = new StringTokenizer(list,",");
|
||||
while (entries.hasMoreTokens())
|
||||
{
|
||||
entry = entries.nextToken();
|
||||
idx = entry.indexOf('/');
|
||||
|
||||
String host = idx > 0?entry.substring(0,idx):entry;
|
||||
String path = idx > 0?entry.substring(idx):"/*";
|
||||
|
||||
host = host.trim();
|
||||
PathMap pathMap = hostMap.get(host);
|
||||
if (pathMap == null)
|
||||
{
|
||||
pathMap = new PathMap(true);
|
||||
hostMap.put(host,pathMap);
|
||||
}
|
||||
if (path != null)
|
||||
{
|
||||
pathMap.put(path,path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Check the request hostname and path against white- and blacklist.
|
||||
*
|
||||
* @param host
|
||||
* hostname to check
|
||||
* @param path
|
||||
* path to check
|
||||
* @return true if request is allowed to be proxied
|
||||
*/
|
||||
public boolean validateDestination(String host, String path)
|
||||
{
|
||||
if (_white.size() > 0)
|
||||
{
|
||||
boolean match = false;
|
||||
|
||||
Object whiteObj = _white.getLazyMatches(host);
|
||||
if (whiteObj != null)
|
||||
{
|
||||
List whiteList = (whiteObj instanceof List)?(List)whiteObj:Collections.singletonList(whiteObj);
|
||||
|
||||
for (Object entry : whiteList)
|
||||
{
|
||||
PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
|
||||
if (match = (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_black.size() > 0)
|
||||
{
|
||||
Object blackObj = _black.getLazyMatches(host);
|
||||
if (blackObj != null)
|
||||
{
|
||||
List blackList = (blackObj instanceof List)?(List)blackObj:Collections.singletonList(blackObj);
|
||||
|
||||
for (Object entry : blackList)
|
||||
{
|
||||
PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
|
||||
if (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see javax.servlet.Servlet#getServletConfig()
|
||||
*/
|
||||
public ServletConfig getServletConfig()
|
||||
{
|
||||
return _config;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Get the hostHeader.
|
||||
*
|
||||
* @return the hostHeader
|
||||
*/
|
||||
public String getHostHeader()
|
||||
{
|
||||
return _hostHeader;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set the hostHeader.
|
||||
*
|
||||
* @param hostHeader
|
||||
* the hostHeader to set
|
||||
*/
|
||||
public void setHostHeader(String hostHeader)
|
||||
{
|
||||
_hostHeader = hostHeader;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
|
||||
*/
|
||||
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
|
||||
{
|
||||
final int debug = _log.isDebugEnabled()?req.hashCode():0;
|
||||
|
||||
final HttpServletRequest request = (HttpServletRequest)req;
|
||||
final HttpServletResponse response = (HttpServletResponse)res;
|
||||
|
||||
if ("CONNECT".equalsIgnoreCase(request.getMethod()))
|
||||
{
|
||||
handleConnect(request,response);
|
||||
}
|
||||
else
|
||||
{
|
||||
final InputStream in = request.getInputStream();
|
||||
final OutputStream out = response.getOutputStream();
|
||||
|
||||
final Continuation continuation = ContinuationSupport.getContinuation(request);
|
||||
|
||||
if (!continuation.isInitial())
|
||||
response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT); // Need better test that isInitial
|
||||
else
|
||||
{
|
||||
|
||||
String uri = request.getRequestURI();
|
||||
if (request.getQueryString() != null)
|
||||
uri += "?" + request.getQueryString();
|
||||
|
||||
HttpURI url = proxyHttpURI(request,uri);
|
||||
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " proxy " + uri + "-->" + url);
|
||||
|
||||
if (url == null)
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpExchange exchange = new HttpExchange()
|
||||
{
|
||||
@Override
|
||||
protected void onRequestCommitted() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRequestComplete() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseComplete() throws IOException
|
||||
{
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " complete");
|
||||
continuation.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseContent(Buffer content) throws IOException
|
||||
{
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " content" + content.length());
|
||||
content.writeTo(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseHeaderComplete() throws IOException
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
|
||||
{
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " " + version + " " + status + " " + reason);
|
||||
|
||||
if (reason != null && reason.length() > 0)
|
||||
response.setStatus(status,reason.toString());
|
||||
else
|
||||
response.setStatus(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResponseHeader(Buffer name, Buffer value) throws IOException
|
||||
{
|
||||
String nameString = name.toString();
|
||||
String s = nameString.toLowerCase(Locale.ENGLISH);
|
||||
if (!_DontProxyHeaders.contains(s) || (HttpHeader.CONNECTION.is(name) && HttpHeaderValue.CLOSE.is(value)))
|
||||
{
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " " + name + ": " + value);
|
||||
|
||||
String filteredHeaderValue = filterResponseHeaderValue(nameString,value.toString(),request);
|
||||
if (filteredHeaderValue != null && filteredHeaderValue.trim().length() > 0)
|
||||
{
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " " + name + ": (filtered): " + filteredHeaderValue);
|
||||
response.addHeader(nameString,filteredHeaderValue);
|
||||
}
|
||||
}
|
||||
else if (debug != 0)
|
||||
_log.debug(debug + " " + name + "! " + value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConnectionFailed(Throwable ex)
|
||||
{
|
||||
handleOnConnectionFailed(ex,request,response);
|
||||
|
||||
// it is possible this might trigger before the
|
||||
// continuation.suspend()
|
||||
if (!continuation.isInitial())
|
||||
{
|
||||
continuation.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Throwable ex)
|
||||
{
|
||||
if (ex instanceof EofException)
|
||||
{
|
||||
_log.ignore(ex);
|
||||
return;
|
||||
}
|
||||
handleOnException(ex,request,response);
|
||||
|
||||
// it is possible this might trigger before the
|
||||
// continuation.suspend()
|
||||
if (!continuation.isInitial())
|
||||
{
|
||||
continuation.complete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExpire()
|
||||
{
|
||||
handleOnExpire(request,response);
|
||||
continuation.complete();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exchange.setScheme((HttpScheme.HTTPS.is(request.getScheme())?HttpScheme.HTTPS:HttpScheme.HTTP).asString());
|
||||
exchange.setMethod(request.getMethod());
|
||||
exchange.setURL(url.toString());
|
||||
exchange.setVersion(HttpVersion.CACHE.get(request.getProtocol()));
|
||||
|
||||
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " " + request.getMethod() + " " + url + " " + request.getProtocol());
|
||||
|
||||
// check connection header
|
||||
String connectionHdr = request.getHeader("Connection");
|
||||
if (connectionHdr != null)
|
||||
{
|
||||
connectionHdr = connectionHdr.toLowerCase(Locale.ENGLISH);
|
||||
if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
|
||||
connectionHdr = null;
|
||||
}
|
||||
|
||||
// force host
|
||||
if (_hostHeader != null)
|
||||
exchange.setRequestHeader("Host",_hostHeader);
|
||||
|
||||
// copy headers
|
||||
boolean xForwardedFor = false;
|
||||
boolean hasContent = false;
|
||||
long contentLength = -1;
|
||||
Enumeration<?> enm = request.getHeaderNames();
|
||||
while (enm.hasMoreElements())
|
||||
{
|
||||
// TODO could be better than this!
|
||||
String hdr = (String)enm.nextElement();
|
||||
String lhdr = hdr.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if (_DontProxyHeaders.contains(lhdr))
|
||||
continue;
|
||||
if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)
|
||||
continue;
|
||||
if (_hostHeader != null && "host".equals(lhdr))
|
||||
continue;
|
||||
|
||||
if ("content-type".equals(lhdr))
|
||||
hasContent = true;
|
||||
else if ("content-length".equals(lhdr))
|
||||
{
|
||||
contentLength = request.getContentLength();
|
||||
exchange.setRequestHeader(HttpHeader.CONTENT_LENGTH.asString(),Long.toString(contentLength));
|
||||
if (contentLength > 0)
|
||||
hasContent = true;
|
||||
}
|
||||
else if ("x-forwarded-for".equals(lhdr))
|
||||
xForwardedFor = true;
|
||||
|
||||
Enumeration<?> vals = request.getHeaders(hdr);
|
||||
while (vals.hasMoreElements())
|
||||
{
|
||||
String val = (String)vals.nextElement();
|
||||
if (val != null)
|
||||
{
|
||||
if (debug != 0)
|
||||
_log.debug(debug + " " + hdr + ": " + val);
|
||||
|
||||
exchange.setRequestHeader(hdr,val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy headers
|
||||
exchange.setRequestHeader("Via","1.1 (jetty)");
|
||||
if (!xForwardedFor)
|
||||
{
|
||||
exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
|
||||
exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
|
||||
exchange.addRequestHeader("X-Forwarded-Host",request.getHeader("Host"));
|
||||
exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
|
||||
}
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
exchange.setRequestContentSource(in);
|
||||
}
|
||||
|
||||
customizeExchange(exchange, request);
|
||||
|
||||
/*
|
||||
* we need to set the timeout on the continuation to take into
|
||||
* account the timeout of the HttpClient and the HttpExchange
|
||||
*/
|
||||
long ctimeout = (_client.getTimeout() > exchange.getTimeout()) ? _client.getTimeout() : exchange.getTimeout();
|
||||
|
||||
// continuation fudge factor of 1000, underlying components
|
||||
// should fail/expire first from exchange
|
||||
if ( ctimeout == 0 )
|
||||
{
|
||||
continuation.setTimeout(0); // ideally never times out
|
||||
}
|
||||
else
|
||||
{
|
||||
continuation.setTimeout(ctimeout + 1000);
|
||||
}
|
||||
|
||||
customizeContinuation(continuation);
|
||||
|
||||
continuation.suspend(response);
|
||||
_client.send(exchange);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void handleConnect(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
String uri = request.getRequestURI();
|
||||
|
||||
String port = "";
|
||||
String host = "";
|
||||
|
||||
int c = uri.indexOf(':');
|
||||
if (c >= 0)
|
||||
{
|
||||
port = uri.substring(c + 1);
|
||||
host = uri.substring(0,c);
|
||||
if (host.indexOf('/') > 0)
|
||||
host = host.substring(host.indexOf('/') + 1);
|
||||
}
|
||||
|
||||
// TODO - make this async!
|
||||
|
||||
InetSocketAddress inetAddress = new InetSocketAddress(host,Integer.parseInt(port));
|
||||
|
||||
// if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
|
||||
// {
|
||||
// sendForbid(request,response,uri);
|
||||
// }
|
||||
// else
|
||||
{
|
||||
InputStream in = request.getInputStream();
|
||||
OutputStream out = response.getOutputStream();
|
||||
|
||||
Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
|
||||
|
||||
response.setStatus(200);
|
||||
response.setHeader("Connection","close");
|
||||
response.flushBuffer();
|
||||
// TODO prevent real close!
|
||||
|
||||
IO.copyThread(socket.getInputStream(),out);
|
||||
IO.copy(in,socket.getOutputStream());
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
|
||||
{
|
||||
return proxyHttpURI(request.getScheme(), request.getServerName(), request.getServerPort(), uri);
|
||||
}
|
||||
|
||||
protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException
|
||||
{
|
||||
if (!validateDestination(serverName,uri))
|
||||
return null;
|
||||
|
||||
return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see javax.servlet.Servlet#getServletInfo()
|
||||
*/
|
||||
public String getServletInfo()
|
||||
{
|
||||
return "Proxy Servlet";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to customize an exchange. Useful for setting timeouts etc. The default implementation does nothing.
|
||||
*
|
||||
* @param exchange
|
||||
* @param request
|
||||
*/
|
||||
protected void customizeExchange(HttpExchange exchange, HttpServletRequest request)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to customize the Continuation after it's initial creation in the service method. Useful for setting timeouts etc. The
|
||||
* default implementation does nothing.
|
||||
*
|
||||
* @param continuation
|
||||
*/
|
||||
protected void customizeContinuation(Continuation continuation)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for custom handling of an HttpExchange's onConnectionFailed method. The default implementation delegates to
|
||||
* {@link #handleOnException(Throwable, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
|
||||
*
|
||||
* @param ex
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
protected void handleOnConnectionFailed(Throwable ex, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
handleOnException(ex,request,response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for custom handling of an HttpExchange's onException method. The default implementation sets the response status to
|
||||
* HttpServletResponse.SC_INTERNAL_SERVER_ERROR (503)
|
||||
*
|
||||
* @param ex
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
protected void handleOnException(Throwable ex, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
if (ex instanceof IOException)
|
||||
{
|
||||
_log.warn(ex.toString());
|
||||
_log.debug(ex);
|
||||
}
|
||||
else
|
||||
_log.warn(ex);
|
||||
|
||||
if (!response.isCommitted())
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for custom handling of an HttpExchange's onExpire method. The default implementation sets the response status to
|
||||
* HttpServletResponse.SC_GATEWAY_TIMEOUT (504)
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
protected void handleOnExpire(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
if (!response.isCommitted())
|
||||
{
|
||||
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for remote server response header filtering. The default implementation returns the header value as is. If null is returned, this header
|
||||
* won't be forwarded back to the client.
|
||||
*
|
||||
* @param headerName
|
||||
* @param headerValue
|
||||
* @param request
|
||||
* @return filteredHeaderValue
|
||||
*/
|
||||
protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
|
||||
{
|
||||
return headerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transparent Proxy.
|
||||
*
|
||||
* This convenience extension to ProxyServlet configures the servlet as a transparent proxy. The servlet is configured with init parameters:
|
||||
* <ul>
|
||||
* <li>ProxyTo - a URI like http://host:80/context to which the request is proxied.
|
||||
* <li>Prefix - a URI prefix that is striped from the start of the forwarded URI.
|
||||
* </ul>
|
||||
* For example, if a request was received at /foo/bar and the ProxyTo was http://host:80/context and the Prefix was /foo, then the request would be proxied
|
||||
* to http://host:80/context/bar
|
||||
*
|
||||
*/
|
||||
public static class Transparent extends ProxyServlet
|
||||
{
|
||||
String _prefix;
|
||||
String _proxyTo;
|
||||
|
||||
public Transparent()
|
||||
{
|
||||
}
|
||||
|
||||
public Transparent(String prefix, String host, int port)
|
||||
{
|
||||
this(prefix,"http",host,port,null);
|
||||
}
|
||||
|
||||
public Transparent(String prefix, String schema, String host, int port, String path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (prefix != null)
|
||||
{
|
||||
_prefix = new URI(prefix).normalize().toString();
|
||||
}
|
||||
_proxyTo = new URI(schema,null,host,port,path,null,null).normalize().toString();
|
||||
}
|
||||
catch (URISyntaxException ex)
|
||||
{
|
||||
_log.debug("Invalid URI syntax",ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException
|
||||
{
|
||||
super.init(config);
|
||||
|
||||
String prefix = config.getInitParameter("Prefix");
|
||||
_prefix = prefix == null?_prefix:prefix;
|
||||
|
||||
// Adjust prefix value to account for context path
|
||||
String contextPath = _context.getContextPath();
|
||||
_prefix = _prefix == null?contextPath:(contextPath + _prefix);
|
||||
|
||||
String proxyTo = config.getInitParameter("ProxyTo");
|
||||
_proxyTo = proxyTo == null?_proxyTo:proxyTo;
|
||||
|
||||
if (_proxyTo == null)
|
||||
throw new UnavailableException("ProxyTo parameter is requred.");
|
||||
|
||||
if (!_prefix.startsWith("/"))
|
||||
throw new UnavailableException("Prefix parameter must start with a '/'.");
|
||||
|
||||
_log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!uri.startsWith(_prefix))
|
||||
return null;
|
||||
|
||||
URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize();
|
||||
|
||||
if (!validateDestination(dstUri.getHost(),dstUri.getPath()))
|
||||
return null;
|
||||
|
||||
return new HttpURI(dstUri.toString());
|
||||
}
|
||||
catch (URISyntaxException ex)
|
||||
{
|
||||
throw new MalformedURLException(ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,218 +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.server.handler;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
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.Handler;
|
||||
import org.eclipse.jetty.server.SelectChannelConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.junit.AfterClass;
|
||||
|
||||
/**
|
||||
* @version $Revision$ $Date$
|
||||
*/
|
||||
public abstract class AbstractConnectHandlerTest
|
||||
{
|
||||
protected static Server server;
|
||||
protected static Connector.NetConnector serverConnector;
|
||||
protected static Server proxy;
|
||||
protected static Connector proxyConnector;
|
||||
|
||||
protected static void startServer(Connector.NetConnector connector, Handler handler) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
serverConnector = connector;
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
protected static void startProxy() throws Exception
|
||||
{
|
||||
proxy = new Server();
|
||||
proxyConnector = new SelectChannelConnector();
|
||||
proxy.addConnector(proxyConnector);
|
||||
proxy.setHandler(new ConnectHandler());
|
||||
proxy.start();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void stop() throws Exception
|
||||
{
|
||||
stopProxy();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
protected static void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
server.join();
|
||||
}
|
||||
|
||||
protected static void stopProxy() throws Exception
|
||||
{
|
||||
proxy.stop();
|
||||
proxy.join();
|
||||
}
|
||||
|
||||
protected Response readResponse(BufferedReader reader) throws IOException
|
||||
{
|
||||
// Simplified parser for HTTP responses
|
||||
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
|
||||
{
|
||||
Socket socket = new Socket("localhost", ((Connector.NetConnector)proxyConnector).getLocalPort());
|
||||
socket.setSoTimeout(10000);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,135 +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 static org.junit.Assert.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BalancerServletTest extends AbstractBalancerServletTest
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testRoundRobinBalancer() throws Exception
|
||||
{
|
||||
setStickySessions(false);
|
||||
startBalancer(CounterServlet.class);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
byte[] responseBytes = sendRequestToBalancer("/");
|
||||
String returnedCounter = readFirstLine(responseBytes);
|
||||
// RR : response should increment every other request
|
||||
String expectedCounter = String.valueOf(i / 2);
|
||||
assertEquals(expectedCounter,returnedCounter);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStickySessionsBalancer() throws Exception
|
||||
{
|
||||
setStickySessions(true);
|
||||
startBalancer(CounterServlet.class);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
byte[] responseBytes = sendRequestToBalancer("/");
|
||||
String returnedCounter = readFirstLine(responseBytes);
|
||||
// RR : response should increment on each request
|
||||
String expectedCounter = String.valueOf(i);
|
||||
assertEquals(expectedCounter,returnedCounter);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyPassReverse() throws Exception
|
||||
{
|
||||
setStickySessions(false);
|
||||
startBalancer(RelocationServlet.class);
|
||||
|
||||
byte[] responseBytes = sendRequestToBalancer("index.html");
|
||||
String msg = readFirstLine(responseBytes);
|
||||
assertEquals("success",msg);
|
||||
}
|
||||
|
||||
private String readFirstLine(byte[] responseBytes) throws IOException
|
||||
{
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(responseBytes)));
|
||||
return reader.readLine();
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final class CounterServlet extends HttpServlet
|
||||
{
|
||||
|
||||
private int counter;
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException
|
||||
{
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
// Force session creation
|
||||
req.getSession();
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().println(counter++);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final class RelocationServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
if (req.getRequestURI().endsWith("/index.html"))
|
||||
{
|
||||
resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo%20molo");
|
||||
return;
|
||||
}
|
||||
resp.setContentType("text/plain");
|
||||
if ("pipo molo".equals(req.getParameter("secret")))
|
||||
{
|
||||
resp.getWriter().println("success");
|
||||
}
|
||||
else
|
||||
{
|
||||
resp.getWriter().println("failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,194 +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.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import junit.framework.Assert;
|
||||
import org.eclipse.jetty.client.ContentExchange;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.proxy.ProxyServlet;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class ProxyServletTest
|
||||
{
|
||||
private Server _server;
|
||||
private Connector _connector;
|
||||
private HttpClient _client;
|
||||
|
||||
public void init(HttpServlet servlet) throws Exception
|
||||
{
|
||||
_server = new Server();
|
||||
|
||||
_connector = new SelectChannelConnector();
|
||||
_server.addConnector(_connector);
|
||||
|
||||
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, "/*");
|
||||
|
||||
ServletContextHandler appCtx = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
|
||||
ServletHolder appServletHolder = new ServletHolder(servlet);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
handlers.addHandler(proxyCtx);
|
||||
handlers.addHandler(appCtx);
|
||||
|
||||
_server.start();
|
||||
|
||||
_client = new HttpClient();
|
||||
_client.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception
|
||||
{
|
||||
if (_client != null)
|
||||
_client.stop();
|
||||
|
||||
if (_server != null)
|
||||
{
|
||||
_server.stop();
|
||||
_server.join();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXForwardedHostHeader() throws Exception
|
||||
{
|
||||
init(new HttpServlet()
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
PrintWriter writer = resp.getWriter();
|
||||
writer.write(req.getHeader("X-Forwarded-Host"));
|
||||
writer.flush();
|
||||
}
|
||||
});
|
||||
|
||||
String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
|
||||
ContentExchange exchange = new ContentExchange();
|
||||
exchange.setURL(url);
|
||||
_client.send(exchange);
|
||||
exchange.waitForDone();
|
||||
assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",exchange.getResponseContent(),equalTo("localhost:"
|
||||
+ _connector.getLocalPort()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigDownloadWithSlowReader() throws Exception
|
||||
{
|
||||
// Create a 6 MiB file
|
||||
final File file = File.createTempFile("test_", null, MavenTestingUtils.getTargetTestingDir());
|
||||
file.deleteOnExit();
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
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()
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
ServletOutputStream output = response.getOutputStream();
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = fis.read(buffer)) >= 0)
|
||||
output.write(buffer, 0, read);
|
||||
fis.close();
|
||||
}
|
||||
});
|
||||
|
||||
String url = "http://localhost:" + _connector.getLocalPort() + "/proxy/test";
|
||||
ContentExchange exchange = new ContentExchange(true)
|
||||
{
|
||||
@Override
|
||||
protected void onResponseContent(Buffer content) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// Slow down the reader
|
||||
TimeUnit.MILLISECONDS.sleep(10);
|
||||
super.onResponseContent(content);
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
throw (IOException)new IOException().initCause(x);
|
||||
}
|
||||
}
|
||||
};
|
||||
exchange.setURL(url);
|
||||
long start = System.nanoTime();
|
||||
_client.send(exchange);
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange.waitForDone());
|
||||
long elapsed = System.nanoTime() - start;
|
||||
Assert.assertEquals(HttpStatus.OK_200, exchange.getResponseStatus());
|
||||
Assert.assertEquals(file.length(), exchange.getResponseContentBytes().length);
|
||||
long millis = TimeUnit.NANOSECONDS.toMillis(elapsed);
|
||||
long rate = file.length() / 1024 * 1000 / millis;
|
||||
System.out.printf("download rate = %d KiB/s%n", rate);
|
||||
}
|
||||
}
|
|
@ -25,12 +25,13 @@ import java.util.concurrent.RejectedExecutionException;
|
|||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpParser;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
|
||||
import org.eclipse.jetty.io.AbstractConnection;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
|
@ -61,7 +62,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
private volatile ByteBuffer _chunk = null;
|
||||
private BlockingCallback _readBlocker = new BlockingCallback();
|
||||
private BlockingCallback _writeBlocker = new BlockingCallback();
|
||||
|
||||
|
||||
// TODO get rid of this
|
||||
private final Runnable _channelRunner = new Runnable()
|
||||
{
|
||||
|
@ -77,7 +78,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
{
|
||||
setCurrentConnection(null);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -410,7 +411,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <C> void send(ResponseInfo info, ByteBuffer content, boolean lastContent, C context, Callback<C> callback)
|
||||
{
|
||||
|
@ -514,6 +515,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
}
|
||||
}
|
||||
|
||||
public ByteBuffer getRequestBuffer()
|
||||
{
|
||||
return _requestBuffer;
|
||||
}
|
||||
|
||||
private class Input extends ByteBufferHttpInput
|
||||
{
|
||||
|
@ -543,7 +548,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
|
||||
// Do we have content ready to parse?
|
||||
if (BufferUtil.isEmpty(_requestBuffer))
|
||||
{
|
||||
{
|
||||
// If no more input
|
||||
if (getEndPoint().isInputShutdown())
|
||||
{
|
||||
|
@ -642,24 +647,32 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
|||
switch (version)
|
||||
{
|
||||
case HTTP_0_9:
|
||||
{
|
||||
persistent = false;
|
||||
break;
|
||||
|
||||
}
|
||||
case HTTP_1_0:
|
||||
{
|
||||
persistent = getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
|
||||
if (!persistent)
|
||||
persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
|
||||
if (persistent)
|
||||
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
|
||||
break;
|
||||
|
||||
}
|
||||
case HTTP_1_1:
|
||||
{
|
||||
persistent = !getRequest().getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||
|
||||
if (!persistent)
|
||||
persistent = HttpMethod.CONNECT.is(getRequest().getMethod());
|
||||
if (!persistent)
|
||||
getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!persistent)
|
||||
|
|
|
@ -0,0 +1,584 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.server.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.io.SelectChannelEndPoint;
|
||||
import org.eclipse.jetty.io.SelectorManager;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConnection;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.util.thread.TimerScheduler;
|
||||
|
||||
/**
|
||||
* <p>Implementation of a {@link Handler} that supports HTTP CONNECT.</p>
|
||||
*/
|
||||
public class ConnectHandler extends HandlerWrapper
|
||||
{
|
||||
protected static final Logger LOG = Log.getLogger(ConnectHandler.class);
|
||||
|
||||
private final Set<String> whiteList = new HashSet<>();
|
||||
private final Set<String> blackList = new HashSet<>();
|
||||
private Executor executor;
|
||||
private Scheduler scheduler;
|
||||
private ByteBufferPool bufferPool;
|
||||
private SelectorManager selector;
|
||||
private long connectTimeout = 15000;
|
||||
private long idleTimeout = 30000;
|
||||
private int bufferSize = 4096;
|
||||
|
||||
public ConnectHandler()
|
||||
{
|
||||
this(null);
|
||||
}
|
||||
|
||||
public ConnectHandler(Handler handler)
|
||||
{
|
||||
setHandler(handler);
|
||||
}
|
||||
|
||||
public Executor getExecutor()
|
||||
{
|
||||
return executor;
|
||||
}
|
||||
|
||||
public void setExecutor(Executor executor)
|
||||
{
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public Scheduler getScheduler()
|
||||
{
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public void setScheduler(Scheduler scheduler)
|
||||
{
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public void setByteBufferPool(ByteBufferPool bufferPool)
|
||||
{
|
||||
this.bufferPool = bufferPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the timeout, in milliseconds, to connect to the remote server
|
||||
*/
|
||||
public long getConnectTimeout()
|
||||
{
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param connectTimeout the timeout, in milliseconds, to connect to the remote server
|
||||
*/
|
||||
public void setConnectTimeout(long connectTimeout)
|
||||
{
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the idle timeout, in milliseconds
|
||||
*/
|
||||
public long getIdleTimeout()
|
||||
{
|
||||
return idleTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param idleTimeout the idle timeout, in milliseconds
|
||||
*/
|
||||
public void setIdleTimeout(long idleTimeout)
|
||||
{
|
||||
this.idleTimeout = idleTimeout;
|
||||
}
|
||||
|
||||
public int getBufferSize()
|
||||
{
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize)
|
||||
{
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (executor == null)
|
||||
{
|
||||
setExecutor(getServer().getThreadPool());
|
||||
}
|
||||
if (scheduler == null)
|
||||
{
|
||||
setScheduler(new TimerScheduler());
|
||||
addBean(getScheduler());
|
||||
}
|
||||
if (bufferPool == null)
|
||||
{
|
||||
setByteBufferPool(new MappedByteBufferPool());
|
||||
addBean(getByteBufferPool());
|
||||
}
|
||||
addBean(selector = newSelectorManager());
|
||||
selector.setConnectTimeout(getConnectTimeout());
|
||||
super.doStart();
|
||||
}
|
||||
|
||||
protected SelectorManager newSelectorManager()
|
||||
{
|
||||
return new Manager(getExecutor(), getScheduler(), 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
if (HttpMethod.CONNECT.is(request.getMethod()))
|
||||
{
|
||||
String serverAddress = request.getRequestURI();
|
||||
LOG.debug("CONNECT request for {}", serverAddress);
|
||||
try
|
||||
{
|
||||
handleConnect(baseRequest, request, response, serverAddress);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
// TODO
|
||||
LOG.warn("ConnectHandler " + baseRequest.getUri() + " " + x);
|
||||
LOG.debug(x);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
super.handle(target, baseRequest, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Handles a CONNECT request.</p>
|
||||
* <p>CONNECT requests may have authentication headers such as {@code Proxy-Authorization}
|
||||
* that authenticate the client with the proxy.</p>
|
||||
*
|
||||
* @param jettyRequest Jetty-specific http request
|
||||
* @param request the http request
|
||||
* @param response the http response
|
||||
* @param serverAddress the remote server address in the form {@code host:port}
|
||||
*/
|
||||
protected void handleConnect(Request jettyRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
|
||||
{
|
||||
jettyRequest.setHandled(true);
|
||||
try
|
||||
{
|
||||
boolean proceed = handleAuthentication(request, response, serverAddress);
|
||||
if (!proceed)
|
||||
{
|
||||
LOG.debug("Missing proxy authentication");
|
||||
sendConnectResponse(request, response, HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
|
||||
return;
|
||||
}
|
||||
|
||||
String host = serverAddress;
|
||||
int port = 80;
|
||||
int colon = serverAddress.indexOf(':');
|
||||
if (colon > 0)
|
||||
{
|
||||
host = serverAddress.substring(0, colon);
|
||||
port = Integer.parseInt(serverAddress.substring(colon + 1));
|
||||
}
|
||||
|
||||
if (!validateDestination(host, port))
|
||||
{
|
||||
LOG.debug("Destination {}:{} forbidden", host, port);
|
||||
sendConnectResponse(request, response, HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
SocketChannel channel = SocketChannel.open();
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.configureBlocking(false);
|
||||
InetSocketAddress address = new InetSocketAddress(host, port);
|
||||
channel.connect(address);
|
||||
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
asyncContext.setTimeout(0);
|
||||
|
||||
LOG.debug("Connecting to {}", address);
|
||||
ConnectContext connectContext = new ConnectContext(request, response, asyncContext, HttpConnection.getCurrentConnection());
|
||||
selector.connect(channel, connectContext);
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
onConnectFailure(request, response, null, x);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onConnectSuccess(ConnectContext connectContext, UpstreamConnection upstreamConnection)
|
||||
{
|
||||
HttpConnection httpConnection = connectContext.getHttpConnection();
|
||||
ByteBuffer requestBuffer = httpConnection.getRequestBuffer();
|
||||
ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
|
||||
int remaining = requestBuffer.remaining();
|
||||
if (remaining > 0)
|
||||
{
|
||||
buffer = bufferPool.acquire(remaining, requestBuffer.isDirect());
|
||||
BufferUtil.flipToFill(buffer);
|
||||
buffer.put(requestBuffer);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
ConcurrentMap<String, Object> context = connectContext.getContext();
|
||||
HttpServletRequest request = connectContext.getRequest();
|
||||
prepareContext(request, context);
|
||||
|
||||
EndPoint downstreamEndPoint = httpConnection.getEndPoint();
|
||||
DownstreamConnection downstreamConnection = newDownstreamConnection(downstreamEndPoint, context, buffer);
|
||||
downstreamConnection.setInputBufferSize(getBufferSize());
|
||||
|
||||
upstreamConnection.setConnection(downstreamConnection);
|
||||
downstreamConnection.setConnection(upstreamConnection);
|
||||
LOG.debug("Connection setup completed: {}<->{}", downstreamConnection, upstreamConnection);
|
||||
|
||||
HttpServletResponse response = connectContext.getResponse();
|
||||
sendConnectResponse(request, response, HttpServletResponse.SC_OK);
|
||||
|
||||
upgradeConnection(request, response, downstreamConnection);
|
||||
connectContext.getAsyncContext().complete();
|
||||
}
|
||||
|
||||
protected void onConnectFailure(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, Throwable failure)
|
||||
{
|
||||
LOG.debug("CONNECT failed", failure);
|
||||
sendConnectResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
if (asyncContext != null)
|
||||
asyncContext.complete();
|
||||
}
|
||||
|
||||
private void sendConnectResponse(HttpServletRequest request, HttpServletResponse response, int statusCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
response.setStatus(statusCode);
|
||||
if (statusCode != HttpServletResponse.SC_OK)
|
||||
response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
|
||||
response.getOutputStream().close();
|
||||
LOG.debug("CONNECT response sent {} {}", request.getProtocol(), response.getStatus());
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
// TODO: nothing we can do, close the connection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Handles the authentication before setting up the tunnel to the remote server.</p>
|
||||
* <p>The default implementation returns true.</p>
|
||||
*
|
||||
* @param request the HTTP request
|
||||
* @param response the HTTP response
|
||||
* @param address the address of the remote server in the form {@code host:port}.
|
||||
* @return true to allow to connect to the remote host, false otherwise
|
||||
*/
|
||||
protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected DownstreamConnection newDownstreamConnection(EndPoint endPoint, ConcurrentMap<String, Object> context, ByteBuffer buffer)
|
||||
{
|
||||
return new DownstreamConnection(endPoint, getExecutor(), getByteBufferPool(), context, buffer);
|
||||
}
|
||||
|
||||
protected UpstreamConnection newUpstreamConnection(EndPoint endPoint, ConnectContext connectContext)
|
||||
{
|
||||
return new UpstreamConnection(endPoint, getExecutor(), getByteBufferPool(), connectContext);
|
||||
}
|
||||
|
||||
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
|
||||
{
|
||||
}
|
||||
|
||||
private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection)
|
||||
{
|
||||
// Set the new connection as request attribute and change the status to 101
|
||||
// so that Jetty understands that it has to upgrade the connection
|
||||
request.setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, connection);
|
||||
response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
|
||||
LOG.debug("Upgraded connection to {}", connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.</p>
|
||||
*
|
||||
* @param endPoint the endPoint to read from
|
||||
* @param buffer the buffer to read data into
|
||||
* @param context the context information related to the connection
|
||||
* @return the number of bytes read (possibly 0 since the read is non-blocking)
|
||||
* or -1 if the channel has been closed remotely
|
||||
* @throws IOException if the endPoint cannot be read
|
||||
*/
|
||||
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException
|
||||
{
|
||||
return endPoint.fill(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Writes (with non-blocking semantic) the given buffer of data onto the given endPoint.</p>
|
||||
*
|
||||
* @param endPoint the endPoint to write to
|
||||
* @param buffer the buffer to write
|
||||
* @param context the context information related to the connection
|
||||
* @param callback the completion callback to invoke
|
||||
*/
|
||||
protected void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context, Callback<Void> callback)
|
||||
{
|
||||
LOG.debug("{} writing {} bytes", this, buffer.remaining());
|
||||
endPoint.write(null, callback, buffer);
|
||||
}
|
||||
|
||||
public Set<String> getWhiteListHosts()
|
||||
{
|
||||
return whiteList;
|
||||
}
|
||||
|
||||
public Set<String> getBlackListHosts()
|
||||
{
|
||||
return blackList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given {@code host} and {@code port} against whitelist and blacklist.
|
||||
*
|
||||
* @param host the host to check
|
||||
* @param port the port to check
|
||||
* @return true if it is allowed to connect to the given host and port
|
||||
*/
|
||||
public boolean validateDestination(String host, int port)
|
||||
{
|
||||
String hostPort = host + ":" + port;
|
||||
if (!whiteList.isEmpty())
|
||||
{
|
||||
if (!whiteList.contains(hostPort))
|
||||
{
|
||||
LOG.debug("Host {}:{} not whitelisted", host, port);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!blackList.isEmpty())
|
||||
{
|
||||
if (blackList.contains(hostPort))
|
||||
{
|
||||
LOG.debug("Host {}:{} blacklisted", host, port);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
dumpThis(out);
|
||||
dump(out, indent, getBeans(), TypeUtil.asList(getHandlers()));
|
||||
}
|
||||
|
||||
protected class Manager extends SelectorManager
|
||||
{
|
||||
|
||||
private Manager(Executor executor, Scheduler scheduler, int selectors)
|
||||
{
|
||||
super(executor, scheduler, selectors);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
|
||||
{
|
||||
return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
|
||||
{
|
||||
ConnectHandler.LOG.debug("Connected to {}", channel.getRemoteAddress());
|
||||
ConnectContext connectContext = (ConnectContext)attachment;
|
||||
UpstreamConnection connection = newUpstreamConnection(endpoint, connectContext);
|
||||
connection.setInputBufferSize(getBufferSize());
|
||||
return connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
||||
{
|
||||
ConnectContext connectContext = (ConnectContext)attachment;
|
||||
onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class ConnectContext
|
||||
{
|
||||
private final ConcurrentMap<String, Object> context = new ConcurrentHashMap<>();
|
||||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
private final AsyncContext asyncContext;
|
||||
private final HttpConnection httpConnection;
|
||||
|
||||
public ConnectContext(HttpServletRequest request, HttpServletResponse response, AsyncContext asyncContext, HttpConnection httpConnection)
|
||||
{
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
this.asyncContext = asyncContext;
|
||||
this.httpConnection = httpConnection;
|
||||
}
|
||||
|
||||
public ConcurrentMap<String, Object> getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
public HttpServletRequest getRequest()
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpServletResponse getResponse()
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
public AsyncContext getAsyncContext()
|
||||
{
|
||||
return asyncContext;
|
||||
}
|
||||
|
||||
public HttpConnection getHttpConnection()
|
||||
{
|
||||
return httpConnection;
|
||||
}
|
||||
}
|
||||
|
||||
public class UpstreamConnection extends ProxyConnection
|
||||
{
|
||||
private ConnectContext connectContext;
|
||||
|
||||
public UpstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConnectContext connectContext)
|
||||
{
|
||||
super(endPoint, executor, bufferPool, connectContext.getContext());
|
||||
this.connectContext = connectContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
onConnectSuccess(connectContext, this);
|
||||
fillInterested();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException
|
||||
{
|
||||
return ConnectHandler.this.read(endPoint, buffer, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context, Callback<Void> callback)
|
||||
{
|
||||
ConnectHandler.this.write(endPoint, buffer, context, callback);
|
||||
}
|
||||
}
|
||||
|
||||
public class DownstreamConnection extends ProxyConnection
|
||||
{
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
public DownstreamConnection(EndPoint endPoint, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context, ByteBuffer buffer)
|
||||
{
|
||||
super(endPoint, executor, bufferPool, context);
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
super.onOpen();
|
||||
final int remaining = buffer.remaining();
|
||||
write(getConnection().getEndPoint(), buffer, getContext(), 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException
|
||||
{
|
||||
return ConnectHandler.this.read(endPoint, buffer, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context, Callback<Void> callback)
|
||||
{
|
||||
ConnectHandler.this.write(endPoint, buffer, context, callback);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.server.handler;
|
||||
|
||||
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 Connection connection;
|
||||
|
||||
protected ProxyConnection(EndPoint endp, Executor executor, ByteBufferPool bufferPool, ConcurrentMap<String, Object> context)
|
||||
{
|
||||
super(endp, executor);
|
||||
this.bufferPool = bufferPool;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ByteBufferPool getByteBufferPool()
|
||||
{
|
||||
return bufferPool;
|
||||
}
|
||||
|
||||
public ConcurrentMap<String, Object> getContext()
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
public Connection getConnection()
|
||||
{
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void setConnection(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 = read(getEndPoint(), buffer, getContext());
|
||||
LOG.debug("{} filled {} bytes", this, filled);
|
||||
if (filled > 0)
|
||||
{
|
||||
write(getConnection().getEndPoint(), buffer, getContext(), 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 abstract int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context) throws IOException;
|
||||
|
||||
protected abstract void write(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap<String, Object> context, Callback<Void> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.server.handler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.NetworkConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
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;
|
||||
|
||||
public abstract class AbstractConnectHandlerTest
|
||||
{
|
||||
protected Server server;
|
||||
protected ServerConnector serverConnector;
|
||||
protected Server proxy;
|
||||
protected Connector proxyConnector;
|
||||
protected ConnectHandler connectHandler;
|
||||
|
||||
protected void prepareProxy() throws Exception
|
||||
{
|
||||
proxy = new Server();
|
||||
proxyConnector = new ServerConnector(proxy);
|
||||
proxy.addConnector(proxyConnector);
|
||||
connectHandler = new ConnectHandler();
|
||||
proxy.setHandler(connectHandler);
|
||||
proxy.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
disposeServer();
|
||||
disposeProxy();
|
||||
}
|
||||
|
||||
protected void disposeServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
|
||||
protected void disposeProxy() throws Exception
|
||||
{
|
||||
proxy.stop();
|
||||
}
|
||||
|
||||
protected SimpleHttpResponse readResponse(BufferedReader reader) throws IOException
|
||||
{
|
||||
return new SimpleHttpParser().readResponse(reader);
|
||||
}
|
||||
|
||||
protected Socket newSocket() throws IOException
|
||||
{
|
||||
Socket socket = new Socket("localhost", ((NetworkConnector)proxyConnector).getLocalPort());
|
||||
socket.setSoTimeout(5000);
|
||||
return socket;
|
||||
}
|
||||
}
|
|
@ -25,65 +25,53 @@ import java.io.InputStream;
|
|||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
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.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
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
|
||||
{
|
||||
@BeforeClass
|
||||
public static void init() throws Exception
|
||||
private SslContextFactory sslContextFactory;
|
||||
|
||||
@Before
|
||||
public void prepare() throws Exception
|
||||
{
|
||||
SslSelectChannelConnector connector = new SslSelectChannelConnector();
|
||||
connector.setMaxIdleTime(3600000); // TODO remove
|
||||
|
||||
sslContextFactory = new SslContextFactory();
|
||||
String keyStorePath = MavenTestingUtils.getTestResourceFile("keystore").getAbsolutePath();
|
||||
SslContextFactory cf = connector.getSslContextFactory();
|
||||
cf.setKeyStorePath(keyStorePath);
|
||||
cf.setKeyStorePassword("storepwd");
|
||||
cf.setKeyManagerPassword("keypwd");
|
||||
|
||||
startServer(connector, new ServerHandler());
|
||||
startProxy();
|
||||
sslContextFactory.setKeyStorePath(keyStorePath);
|
||||
sslContextFactory.setKeyStorePassword("storepwd");
|
||||
sslContextFactory.setKeyManagerPassword("keypwd");
|
||||
server = new Server();
|
||||
serverConnector = new ServerConnector(server, sslContextFactory);
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(new ServerHandler());
|
||||
server.start();
|
||||
prepareProxy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGETRequest() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
socket.setSoTimeout(3600000); // TODO remove
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -92,52 +80,41 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
System.err.println(response);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
// Be sure the buffered input does not have anything buffered
|
||||
assertFalse(input.ready());
|
||||
Assert.assertFalse(input.ready());
|
||||
|
||||
// Upgrade the socket to SSL
|
||||
SSLSocket sslSocket = wrapSocket(socket);
|
||||
try
|
||||
try (SSLSocket sslSocket = wrapSocket(socket))
|
||||
{
|
||||
output = sslSocket.getOutputStream();
|
||||
input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
|
||||
|
||||
request =
|
||||
"GET /echo HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
output.write(request.getBytes("UTF-8"));
|
||||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
sslSocket.close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPOSTRequests() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -146,15 +123,14 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
// Be sure the buffered input does not have anything buffered
|
||||
assertFalse(input.ready());
|
||||
Assert.assertFalse(input.ready());
|
||||
|
||||
// Upgrade the socket to SSL
|
||||
SSLSocket sslSocket = wrapSocket(socket);
|
||||
try
|
||||
try (SSLSocket sslSocket = wrapSocket(socket))
|
||||
{
|
||||
output = sslSocket.getOutputStream();
|
||||
input = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
|
||||
|
@ -171,25 +147,16 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody());
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("POST /echo?param=" + i + "\r\nHELLO", response.getBody());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
sslSocket.close();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
private SSLSocket wrapSocket(Socket socket) throws Exception
|
||||
{
|
||||
SSLContext sslContext = SSLContext.getInstance("SSLv3");
|
||||
sslContext.init(null, new TrustManager[]{new AlwaysTrustManager()}, new SecureRandom());
|
||||
SSLContext sslContext = sslContextFactory.getSslContext();
|
||||
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
|
||||
SSLSocket sslSocket = (SSLSocket)socketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
|
||||
sslSocket.setUseClientMode(true);
|
||||
|
@ -197,22 +164,6 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
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
|
||||
{
|
||||
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
|
@ -229,7 +180,7 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
InputStream input = httpRequest.getInputStream();
|
||||
int read = -1;
|
||||
int read;
|
||||
while ((read = input.read()) >= 0)
|
||||
baos.write(read);
|
||||
baos.close();
|
|
@ -18,9 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -29,51 +26,48 @@ import java.io.InputStreamReader;
|
|||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
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.SelectChannelConnector;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.toolchain.test.OS;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
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.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @version $Revision$ $Date$
|
||||
*/
|
||||
@Ignore
|
||||
public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
||||
{
|
||||
@BeforeClass
|
||||
public static void init() throws Exception
|
||||
@Before
|
||||
public void prepare() throws Exception
|
||||
{
|
||||
startServer(new SelectChannelConnector(), new ServerHandler());
|
||||
startProxy();
|
||||
server = new Server();
|
||||
serverConnector = new ServerConnector(server);
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(new ServerHandler());
|
||||
server.start();
|
||||
prepareProxy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECT() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -82,26 +76,20 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndGET() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
socket.setSoTimeout(30000);
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -110,8 +98,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
request = "" +
|
||||
"GET /echo" + " HTTP/1.1\r\n" +
|
||||
|
@ -121,38 +109,229 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
public void testCONNECTBadHostPort() throws Exception
|
||||
{
|
||||
String invalidHostname = "AMAZEBALLS_BADHOST.webtide.com";
|
||||
|
||||
String invalidHostname = "badHost.webtide.com";
|
||||
|
||||
try
|
||||
{
|
||||
InetAddress addr = InetAddress.getByName(invalidHostname);
|
||||
InetAddress address = InetAddress.getByName(invalidHostname);
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("DNS Hijacking detected: ");
|
||||
err.append(invalidHostname).append(" should have not returned a valid IP address [");
|
||||
err.append(addr.getHostAddress()).append("]. ");
|
||||
err.append(address.getHostAddress()).append("]. ");
|
||||
err.append("Fix your DNS provider to have this test pass.");
|
||||
err.append("\nFor more info see https://en.wikipedia.org/wiki/DNS_hijacking");
|
||||
Assert.assertNull(err.toString(), addr);
|
||||
Assert.assertNull(err.toString(), address);
|
||||
}
|
||||
catch (UnknownHostException e)
|
||||
{
|
||||
// expected path
|
||||
}
|
||||
|
||||
String hostPort = String.format("%s:%d",invalidHostname,serverConnector.getLocalPort());
|
||||
|
||||
String hostPort = String.format("%s:%d", invalidHostname, serverConnector.getLocalPort());
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
|
@ -168,25 +347,24 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 500 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("Response Code", "500", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("Response Code", "500", response.getCode());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCONNECT10AndGET() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.0\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -195,8 +373,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
request = "" +
|
||||
"GET /echo" + " HTTP/1.1\r\n" +
|
||||
|
@ -206,19 +384,15 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndGETPipelined() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
|
@ -226,8 +400,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
"GET /echo" + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -236,30 +409,25 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
// The pipelined request must have gone up to the server as is
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndMultipleGETs() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -268,8 +436,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
|
@ -281,26 +449,21 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndGETServerStop() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -309,8 +472,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
request = "" +
|
||||
"GET /echo HTTP/1.1\r\n" +
|
||||
|
@ -320,33 +483,26 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
|
||||
// Idle server is shut down
|
||||
stopServer();
|
||||
disposeServer();
|
||||
|
||||
int read = input.read();
|
||||
assertEquals(-1, read);
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
// Restart the server for the next test
|
||||
server.start();
|
||||
Assert.assertEquals(-1, read);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndGETAndServerSideClose() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -355,8 +511,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
request = "" +
|
||||
"GET /close HTTP/1.1\r\n" +
|
||||
|
@ -366,24 +522,19 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
int read = input.read();
|
||||
assertEquals(-1, read);
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals(-1, read);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndPOSTAndGET() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -392,8 +543,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
request = "" +
|
||||
"POST /echo HTTP/1.1\r\n" +
|
||||
|
@ -405,8 +556,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("POST /echo\r\nHELLO", response.getBody());
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("POST /echo\r\nHELLO", response.getBody());
|
||||
|
||||
request = "" +
|
||||
"GET /echo" + " HTTP/1.1\r\n" +
|
||||
|
@ -416,34 +567,21 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndPOSTWithBigBody() throws Exception
|
||||
{
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)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 hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -452,8 +590,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
String chunk = "0123456789ABCDEF";
|
||||
|
@ -470,12 +608,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("POST /echo\r\n" + body, response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("POST /echo\r\n" + body, response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,54 +620,46 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
final String contextValue = "contextValue";
|
||||
|
||||
// Replace the default ProxyHandler with a subclass to test context information passing
|
||||
stopProxy();
|
||||
disposeProxy();
|
||||
proxy.setHandler(new ConnectHandler()
|
||||
{
|
||||
@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);
|
||||
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
|
||||
protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> 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));
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@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));
|
||||
return super.write(endPoint, buffer, context);
|
||||
Assert.assertEquals(contextValue, context.get(contextKey));
|
||||
super.write(endPoint, buffer, context, callback);
|
||||
}
|
||||
});
|
||||
proxy.start();
|
||||
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -542,8 +668,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
String body = "0123456789ABCDEF";
|
||||
request = "" +
|
||||
|
@ -556,22 +682,15 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("POST /echo\r\n" + body, response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("POST /echo\r\n" + body, response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndGETPipelinedAndOutputShutdown() throws Exception
|
||||
{
|
||||
// TODO needs to be further investigated
|
||||
assumeTrue(!OS.IS_OSX);
|
||||
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
|
@ -579,8 +698,7 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
"GET /echo" + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -590,33 +708,25 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
socket.shutdownOutput();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
// The pipelined request must have gone up to the server as is
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCONNECTAndGETAndOutputShutdown() throws Exception
|
||||
{
|
||||
// TODO needs to be further investigated
|
||||
assumeTrue(!OS.IS_OSX);
|
||||
|
||||
String hostPort = "localhost:" + ((Connector.NetConnector)serverConnector).getLocalPort();
|
||||
String hostPort = "localhost:" + serverConnector.getLocalPort();
|
||||
String request = "" +
|
||||
"CONNECT " + hostPort + " HTTP/1.1\r\n" +
|
||||
"Host: " + hostPort + "\r\n" +
|
||||
"\r\n";
|
||||
Socket socket = newSocket();
|
||||
try
|
||||
try (Socket socket = newSocket())
|
||||
{
|
||||
OutputStream output = socket.getOutputStream();
|
||||
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
@ -625,8 +735,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
output.flush();
|
||||
|
||||
// Expect 200 OK from the CONNECT request
|
||||
Response response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
SimpleHttpResponse response = readResponse(input);
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
|
||||
request = "" +
|
||||
"GET /echo" + " HTTP/1.1\r\n" +
|
||||
|
@ -638,12 +748,8 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
|
||||
// The pipelined request must have gone up to the server as is
|
||||
response = readResponse(input);
|
||||
assertEquals("200", response.getCode());
|
||||
assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
finally
|
||||
{
|
||||
socket.close();
|
||||
Assert.assertEquals("200", response.getCode());
|
||||
Assert.assertEquals("GET /echo", response.getBody());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,31 +760,36 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
request.setHandled(true);
|
||||
|
||||
String uri = httpRequest.getRequestURI();
|
||||
if ("/echo".equals(uri))
|
||||
switch (uri)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(httpRequest.getMethod()).append(" ").append(uri);
|
||||
if (httpRequest.getQueryString() != null)
|
||||
builder.append("?").append(httpRequest.getQueryString());
|
||||
case "/echo":
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(httpRequest.getMethod()).append(" ").append(uri);
|
||||
if (httpRequest.getQueryString() != null)
|
||||
builder.append("?").append(httpRequest.getQueryString());
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
InputStream input = httpRequest.getInputStream();
|
||||
int read = -1;
|
||||
while ((read = input.read()) >= 0)
|
||||
baos.write(read);
|
||||
baos.close();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
InputStream input = httpRequest.getInputStream();
|
||||
int read;
|
||||
while ((read = input.read()) >= 0)
|
||||
baos.write(read);
|
||||
baos.close();
|
||||
|
||||
ServletOutputStream output = httpResponse.getOutputStream();
|
||||
output.println(builder.toString());
|
||||
output.write(baos.toByteArray());
|
||||
}
|
||||
else if ("/close".equals(uri))
|
||||
{
|
||||
request.getHttpChannel().getConnection().getEndPoint().close();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ServletException();
|
||||
ServletOutputStream output = httpResponse.getOutputStream();
|
||||
output.println(builder.toString());
|
||||
output.write(baos.toByteArray());
|
||||
break;
|
||||
}
|
||||
case "/close":
|
||||
{
|
||||
request.getHttpChannel().getEndPoint().close();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
throw new ServletException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
|
@ -54,13 +54,13 @@
|
|||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-continuation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-continuation</artifactId>
|
||||
<artifactId>jetty-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -84,5 +84,10 @@
|
|||
<artifactId>javax.servlet</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||
|
||||
<!-- ============================================================= -->
|
||||
<!-- Configure the Jetty Server instance with an ID "Proxy" -->
|
||||
<!-- by adding a proxy functionalities. -->
|
||||
<!-- ============================================================= -->
|
||||
<Configure id="Proxy" class="org.eclipse.jetty.server.Server">
|
||||
|
||||
<Arg name="threadpool">
|
||||
<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
|
||||
<Set name="minThreads">16</Set>
|
||||
<Set name="maxThreads">256</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
|
||||
<Call name="addConnector">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||
<Arg name="server"><Ref id="Server" /></Arg>
|
||||
<Set name="host"><Property name="jetty.host" /></Set>
|
||||
<Set name="port"><Property name="jetty.port" default="8888"/></Set>
|
||||
<Set name="idleTimeout">300000</Set>
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
||||
<Set name="handler">
|
||||
<New class="org.eclipse.jetty.server.handler.ConnectHandler">
|
||||
<Set name="handler">
|
||||
<New class="org.eclipse.jetty.servlet.ServletHandler">
|
||||
<Call id="proxyHolder" name="addServletWithMapping">
|
||||
<Arg>org.eclipse.jetty.servlets.ProxyServlet</Arg>
|
||||
<Arg>/</Arg>
|
||||
</Call>
|
||||
</New>
|
||||
</Set>
|
||||
</New>
|
||||
</Set>
|
||||
|
||||
<Ref id="proxyHolder">
|
||||
<Call name="setInitParameter">
|
||||
<Arg>maxThreads</Arg>
|
||||
<Arg>128</Arg>
|
||||
</Call>
|
||||
</Ref>
|
||||
|
||||
<Set name="stopAtShutdown">true</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">true</Set>
|
||||
<Set name="stopTimeout">1000</Set>
|
||||
|
||||
</Configure>
|
|
@ -0,0 +1,307 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class BalancerServlet extends ProxyServlet
|
||||
{
|
||||
private static final String BALANCER_MEMBER_PREFIX = "balancerMember.";
|
||||
private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
|
||||
|
||||
static
|
||||
{
|
||||
List<String> params = new LinkedList<>();
|
||||
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<>();
|
||||
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 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)
|
||||
{
|
||||
_name = name;
|
||||
_proxyTo = proxyTo;
|
||||
_backendURI = URI.create(_proxyTo).normalize();
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
public String getProxyTo()
|
||||
{
|
||||
return _proxyTo;
|
||||
}
|
||||
|
||||
public URI getBackendURI()
|
||||
{
|
||||
return _backendURI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s[name=%s,proxyTo=%s]", getClass().getSimpleName(), _name, _proxyTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return _name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
BalancerMember that = (BalancerMember)obj;
|
||||
return _name.equals(that._name);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,713 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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 java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.UnavailableException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
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.util.InputStreamContentProvider;
|
||||
import org.eclipse.jetty.client.util.TimedResponseListener;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
||||
/**
|
||||
* Asynchronous ProxyServlet.
|
||||
* <p/>
|
||||
* Forwards requests to another server either as a standard web reverse proxy
|
||||
* (as defined by RFC2616) or as a transparent reverse proxy.
|
||||
* <p/>
|
||||
* To facilitate JMX monitoring, the {@link HttpClient} instance is set as context attribute,
|
||||
* prefixed with the servlet's name and exposed by the mechanism provided by
|
||||
* {@link ContextHandler#MANAGED_ATTRIBUTES}.
|
||||
* <p/>
|
||||
* The following init parameters may be used to configure the servlet:
|
||||
* <ul>
|
||||
* <li>hostHeader - forces the host header to a particular value</li>
|
||||
* <li>viaHost - the name to use in the Via header: Via: http/1.1 <viaHost></li>
|
||||
* <li>whiteList - comma-separated list of allowed proxy hosts</li>
|
||||
* <li>blackList - comma-separated list of forbidden proxy hosts</li>
|
||||
* </ul>
|
||||
* <p/>
|
||||
* In addition, see {@link #createHttpClient()} for init parameters used to configure
|
||||
* the {@link HttpClient} instance.
|
||||
*
|
||||
* @see ConnectHandler
|
||||
*/
|
||||
public class ProxyServlet extends HttpServlet
|
||||
{
|
||||
protected static final String ASYNC_CONTEXT = ProxyServlet.class.getName() + ".asyncContext";
|
||||
private static final Set<String> HOP_HEADERS = new HashSet<>();
|
||||
static
|
||||
{
|
||||
HOP_HEADERS.add("proxy-connection");
|
||||
HOP_HEADERS.add("connection");
|
||||
HOP_HEADERS.add("keep-alive");
|
||||
HOP_HEADERS.add("transfer-encoding");
|
||||
HOP_HEADERS.add("te");
|
||||
HOP_HEADERS.add("trailer");
|
||||
HOP_HEADERS.add("proxy-authorization");
|
||||
HOP_HEADERS.add("proxy-authenticate");
|
||||
HOP_HEADERS.add("upgrade");
|
||||
}
|
||||
|
||||
private final Set<String> _whiteList = new HashSet<>();
|
||||
private final Set<String> _blackList = new HashSet<>();
|
||||
|
||||
protected Logger _log;
|
||||
private String _hostHeader;
|
||||
private String _viaHost;
|
||||
private HttpClient _client;
|
||||
private long _timeout;
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException
|
||||
{
|
||||
_log = createLogger();
|
||||
|
||||
ServletConfig config = getServletConfig();
|
||||
|
||||
_hostHeader = config.getInitParameter("hostHeader");
|
||||
|
||||
_viaHost = config.getInitParameter("viaHost");
|
||||
if (_viaHost == null)
|
||||
_viaHost = viaHost();
|
||||
|
||||
try
|
||||
{
|
||||
_client = createHttpClient();
|
||||
|
||||
// Put the HttpClient in the context to leverage ContextHandler.MANAGED_ATTRIBUTES
|
||||
getServletContext().setAttribute(config.getServletName() + ".HttpClient", _client);
|
||||
|
||||
String whiteList = config.getInitParameter("whiteList");
|
||||
if (whiteList != null)
|
||||
getWhiteListHosts().addAll(parseList(whiteList));
|
||||
|
||||
String blackList = config.getInitParameter("blackList");
|
||||
if (blackList != null)
|
||||
getBlackListHosts().addAll(parseList(blackList));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public long getTimeout()
|
||||
{
|
||||
return _timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(long timeout)
|
||||
{
|
||||
this._timeout = timeout;
|
||||
}
|
||||
|
||||
public Set<String> getWhiteListHosts()
|
||||
{
|
||||
return _whiteList;
|
||||
}
|
||||
|
||||
public Set<String> getBlackListHosts()
|
||||
{
|
||||
return _blackList;
|
||||
}
|
||||
|
||||
protected static String viaHost()
|
||||
{
|
||||
try
|
||||
{
|
||||
return InetAddress.getLocalHost().getHostName();
|
||||
}
|
||||
catch (UnknownHostException x)
|
||||
{
|
||||
return "localhost";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a logger instance with a name derived from this servlet's name.
|
||||
*/
|
||||
protected Logger createLogger()
|
||||
{
|
||||
return Log.getLogger(getServletConfig().getServletName());
|
||||
}
|
||||
|
||||
public void destroy()
|
||||
{
|
||||
try
|
||||
{
|
||||
_client.stop();
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
_log.debug(x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link HttpClient} instance, configured with init parameters of this servlet.
|
||||
* <p/>
|
||||
* The init parameters used to configure the {@link HttpClient} instance are:
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th>init-param</th>
|
||||
* <th>default</th>
|
||||
* <th>description</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <td>maxThreads</td>
|
||||
* <td>256</td>
|
||||
* <td>The max number of threads of HttpClient's Executor</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>maxConnections</td>
|
||||
* <td>32768</td>
|
||||
* <td>The max number of connection per address, see {@link HttpClient#setMaxConnectionsPerAddress(int)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>idleTimeout</td>
|
||||
* <td>30000</td>
|
||||
* <td>The idle timeout in milliseconds, see {@link HttpClient#setIdleTimeout(long)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>timeout</td>
|
||||
* <td>60000</td>
|
||||
* <td>The total timeout in milliseconds, see {@link TimedResponseListener}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>requestBufferSize</td>
|
||||
* <td>HttpClient's default</td>
|
||||
* <td>The request buffer size, see {@link HttpClient#setRequestBufferSize(int)}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>responseBufferSize</td>
|
||||
* <td>HttpClient's default</td>
|
||||
* <td>The response buffer size, see {@link HttpClient#setResponseBufferSize(int)}</td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* @return a {@link HttpClient} configured from the {@link #getServletConfig() servlet configuration}
|
||||
* @throws ServletException if the {@link HttpClient} cannot be created
|
||||
*/
|
||||
protected HttpClient createHttpClient() throws ServletException
|
||||
{
|
||||
ServletConfig config = getServletConfig();
|
||||
|
||||
HttpClient client = newHttpClient();
|
||||
// Redirects must be proxied as is, not followed
|
||||
client.setFollowRedirects(false);
|
||||
|
||||
String value = config.getInitParameter("maxThreads");
|
||||
if (value == null)
|
||||
value = "256";
|
||||
QueuedThreadPool executor = new QueuedThreadPool(Integer.parseInt(value));
|
||||
String servletName = config.getServletName();
|
||||
int dot = servletName.lastIndexOf('.');
|
||||
if (dot >= 0)
|
||||
servletName = servletName.substring(dot + 1);
|
||||
executor.setName(servletName);
|
||||
client.setExecutor(executor);
|
||||
|
||||
value = config.getInitParameter("maxConnections");
|
||||
if (value == null)
|
||||
value = "32768";
|
||||
client.setMaxConnectionsPerAddress(Integer.parseInt(value));
|
||||
|
||||
value = config.getInitParameter("idleTimeout");
|
||||
if (value == null)
|
||||
value = "30000";
|
||||
client.setIdleTimeout(Long.parseLong(value));
|
||||
|
||||
value = config.getInitParameter("timeout");
|
||||
if (value == null)
|
||||
value = "60000";
|
||||
_timeout = Long.parseLong(value);
|
||||
|
||||
value = config.getInitParameter("requestBufferSize");
|
||||
if (value != null)
|
||||
client.setRequestBufferSize(Integer.parseInt(value));
|
||||
|
||||
value = config.getInitParameter("responseBufferSize");
|
||||
if (value != null)
|
||||
client.setResponseBufferSize(Integer.parseInt(value));
|
||||
|
||||
try
|
||||
{
|
||||
client.start();
|
||||
|
||||
// Content must not be decoded, otherwise the client gets confused
|
||||
client.getContentDecoderFactories().clear();
|
||||
|
||||
return client;
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
throw new ServletException(x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new HttpClient instance
|
||||
*/
|
||||
protected HttpClient newHttpClient()
|
||||
{
|
||||
return new HttpClient();
|
||||
}
|
||||
|
||||
private Set<String> parseList(String list)
|
||||
{
|
||||
Set<String> result = new HashSet<>();
|
||||
String[] hosts = list.split(",");
|
||||
for (String host : hosts)
|
||||
{
|
||||
host = host.trim();
|
||||
if (host.length() == 0)
|
||||
continue;
|
||||
result.add(host);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given {@code host} and {@code port} against whitelist and blacklist.
|
||||
*
|
||||
* @param host the host to check
|
||||
* @param port the port to check
|
||||
* @return true if it is allowed to be proxy to the given host and port
|
||||
*/
|
||||
public boolean validateDestination(String host, int port)
|
||||
{
|
||||
String hostPort = host + ":" + port;
|
||||
if (!_whiteList.isEmpty())
|
||||
{
|
||||
if (!_whiteList.contains(hostPort))
|
||||
{
|
||||
_log.debug("Host {}:{} not whitelisted", host, port);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!_blackList.isEmpty())
|
||||
{
|
||||
if (_blackList.contains(hostPort))
|
||||
{
|
||||
_log.debug("Host {}:{} blacklisted", host, port);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
final int requestId = getRequestId(request);
|
||||
|
||||
URI rewrittenURI = rewriteURI(request);
|
||||
|
||||
if (_log.isDebugEnabled())
|
||||
{
|
||||
StringBuffer uri = request.getRequestURL();
|
||||
if (request.getQueryString() != null)
|
||||
uri.append("?").append(request.getQueryString());
|
||||
_log.debug("{} rewriting: {} -> {}", requestId, uri, rewrittenURI);
|
||||
}
|
||||
|
||||
if (rewrittenURI == null)
|
||||
{
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
return;
|
||||
}
|
||||
|
||||
final Request proxyRequest = _client.newRequest(rewrittenURI)
|
||||
.method(HttpMethod.fromString(request.getMethod()))
|
||||
.version(HttpVersion.fromString(request.getProtocol()));
|
||||
|
||||
// Copy headers
|
||||
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
|
||||
{
|
||||
String headerName = headerNames.nextElement();
|
||||
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
|
||||
|
||||
// Remove hop-by-hop headers
|
||||
if (HOP_HEADERS.contains(lowerHeaderName))
|
||||
continue;
|
||||
|
||||
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
|
||||
{
|
||||
String headerValue = headerValues.nextElement();
|
||||
if (headerValue != null)
|
||||
proxyRequest.header(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Force the Host header if configured
|
||||
if (_hostHeader != null)
|
||||
proxyRequest.header("Host", _hostHeader);
|
||||
|
||||
// Add proxy headers
|
||||
proxyRequest.header("Via", "http/1.1 " + _viaHost);
|
||||
proxyRequest.header("X-Forwarded-For", request.getRemoteAddr());
|
||||
proxyRequest.header("X-Forwarded-Proto", request.getScheme());
|
||||
proxyRequest.header("X-Forwarded-Host", request.getHeader("Host"));
|
||||
proxyRequest.header("X-Forwarded-Server", request.getLocalName());
|
||||
|
||||
proxyRequest.content(new InputStreamContentProvider(request.getInputStream())
|
||||
{
|
||||
@Override
|
||||
public long getLength()
|
||||
{
|
||||
return request.getContentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ByteBuffer onRead(byte[] buffer, int offset, int length)
|
||||
{
|
||||
_log.debug("{} proxying content to upstream: {} bytes", requestId, length);
|
||||
return super.onRead(buffer, offset, length);
|
||||
}
|
||||
});
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync();
|
||||
// We do not timeout the continuation, but the proxy request
|
||||
asyncContext.setTimeout(0);
|
||||
request.setAttribute(ASYNC_CONTEXT, asyncContext);
|
||||
|
||||
customizeProxyRequest(proxyRequest, request);
|
||||
|
||||
if (_log.isDebugEnabled())
|
||||
{
|
||||
StringBuilder builder = new StringBuilder(request.getMethod());
|
||||
builder.append(" ").append(request.getRequestURI());
|
||||
String query = request.getQueryString();
|
||||
if (query != null)
|
||||
builder.append("?").append(query);
|
||||
builder.append(" ").append(request.getProtocol()).append("\r\n");
|
||||
for (Enumeration<String> headerNames = request.getHeaderNames(); headerNames.hasMoreElements();)
|
||||
{
|
||||
String headerName = headerNames.nextElement();
|
||||
builder.append(headerName).append(": ");
|
||||
for (Enumeration<String> headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();)
|
||||
{
|
||||
String headerValue = headerValues.nextElement();
|
||||
if (headerValue != null)
|
||||
builder.append(headerValue);
|
||||
if (headerValues.hasMoreElements())
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append("\r\n");
|
||||
}
|
||||
builder.append("\r\n");
|
||||
|
||||
_log.debug("{} proxying to upstream:{}{}{}{}",
|
||||
requestId,
|
||||
System.lineSeparator(),
|
||||
builder,
|
||||
proxyRequest,
|
||||
System.lineSeparator(),
|
||||
proxyRequest.getHeaders().toString().trim());
|
||||
}
|
||||
|
||||
proxyRequest.send(new TimedResponseListener(getTimeout(), TimeUnit.MILLISECONDS, proxyRequest, new ProxyResponseListener(request, response)));
|
||||
}
|
||||
|
||||
protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
|
||||
{
|
||||
for (HttpFields.Field field : proxyResponse.getHeaders())
|
||||
{
|
||||
String headerName = field.getName();
|
||||
String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH);
|
||||
if (HOP_HEADERS.contains(lowerHeaderName))
|
||||
continue;
|
||||
|
||||
String newHeaderValue = filterResponseHeader(request, headerName, field.getValue());
|
||||
if (newHeaderValue == null || newHeaderValue.trim().length() == 0)
|
||||
continue;
|
||||
|
||||
response.addHeader(headerName, newHeaderValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException
|
||||
{
|
||||
response.getOutputStream().write(buffer, offset, length);
|
||||
_log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length);
|
||||
}
|
||||
|
||||
protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse)
|
||||
{
|
||||
AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
|
||||
asyncContext.complete();
|
||||
_log.debug("{} proxying successful", getRequestId(request));
|
||||
}
|
||||
|
||||
protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure)
|
||||
{
|
||||
_log.debug(getRequestId(request) + " proxying failed", failure);
|
||||
if (!response.isCommitted())
|
||||
{
|
||||
if (failure instanceof TimeoutException)
|
||||
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
|
||||
else
|
||||
response.setStatus(HttpServletResponse.SC_BAD_GATEWAY);
|
||||
}
|
||||
AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT);
|
||||
asyncContext.complete();
|
||||
}
|
||||
|
||||
protected int getRequestId(HttpServletRequest request)
|
||||
{
|
||||
return System.identityHashCode(request);
|
||||
}
|
||||
|
||||
protected URI rewriteURI(HttpServletRequest request)
|
||||
{
|
||||
if (!validateDestination(request.getServerName(), request.getServerPort()))
|
||||
return null;
|
||||
|
||||
StringBuffer uri = request.getRequestURL();
|
||||
String query = request.getQueryString();
|
||||
if (query != null)
|
||||
uri.append("?").append(query);
|
||||
|
||||
return URI.create(uri.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to customize the proxy request.
|
||||
* The default implementation does nothing.
|
||||
*
|
||||
* @param proxyRequest the proxy request to customize
|
||||
* @param request the request to be proxied
|
||||
*/
|
||||
protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for remote server response header filtering.
|
||||
* The default implementation returns the header value as is.
|
||||
* If null is returned, this header won't be forwarded back to the client.
|
||||
*
|
||||
* @param headerName the header name
|
||||
* @param headerValue the header value
|
||||
* @param request the request to proxy
|
||||
* @return filteredHeaderValue the new header value
|
||||
*/
|
||||
protected String filterResponseHeader(HttpServletRequest request, String headerName, String headerValue)
|
||||
{
|
||||
return headerValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transparent Proxy.
|
||||
* <p/>
|
||||
* This convenience extension to ProxyServlet configures the servlet as a transparent proxy.
|
||||
* The servlet is configured with init parameters:
|
||||
* <ul>
|
||||
* <li>proxyTo - a URI like http://host:80/context to which the request is proxied.
|
||||
* <li>prefix - a URI prefix that is striped from the start of the forwarded URI.
|
||||
* </ul>
|
||||
* For example, if a request is received at /foo/bar and the 'proxyTo' parameter is "http://host:80/context"
|
||||
* and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar".
|
||||
*/
|
||||
public static class Transparent extends ProxyServlet
|
||||
{
|
||||
private String _proxyTo;
|
||||
private String _prefix;
|
||||
|
||||
public Transparent()
|
||||
{
|
||||
}
|
||||
|
||||
public Transparent(String proxyTo, String prefix)
|
||||
{
|
||||
_proxyTo = URI.create(proxyTo).normalize().toString();
|
||||
_prefix = URI.create(prefix).normalize().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws ServletException
|
||||
{
|
||||
super.init();
|
||||
|
||||
ServletConfig config = getServletConfig();
|
||||
|
||||
String prefix = config.getInitParameter("prefix");
|
||||
_prefix = prefix == null ? _prefix : prefix;
|
||||
|
||||
// Adjust prefix value to account for context path
|
||||
String contextPath = getServletContext().getContextPath();
|
||||
_prefix = _prefix == null ? contextPath : (contextPath + _prefix);
|
||||
|
||||
String proxyTo = config.getInitParameter("proxyTo");
|
||||
_proxyTo = proxyTo == null ? _proxyTo : proxyTo;
|
||||
|
||||
if (_proxyTo == null)
|
||||
throw new UnavailableException("Init parameter 'proxyTo' is required.");
|
||||
|
||||
if (!_prefix.startsWith("/"))
|
||||
throw new UnavailableException("Init parameter 'prefix' parameter must start with a '/'.");
|
||||
|
||||
_log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URI rewriteURI(HttpServletRequest request)
|
||||
{
|
||||
String path = request.getRequestURI();
|
||||
if (!path.startsWith(_prefix))
|
||||
return null;
|
||||
|
||||
URI rewrittenURI = URI.create(_proxyTo + path.substring(_prefix.length())).normalize();
|
||||
|
||||
if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort()))
|
||||
return null;
|
||||
|
||||
return rewrittenURI;
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyResponseListener extends Response.Listener.Empty
|
||||
{
|
||||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
|
||||
public ProxyResponseListener(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(Response proxyResponse)
|
||||
{
|
||||
response.setStatus(proxyResponse.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Response proxyResponse)
|
||||
{
|
||||
onResponseHeaders(request, response, proxyResponse);
|
||||
|
||||
if (_log.isDebugEnabled())
|
||||
{
|
||||
StringBuilder builder = new StringBuilder("\r\n");
|
||||
builder.append(request.getProtocol()).append(" ").append(response.getStatus()).append(" ").append(proxyResponse.getReason()).append("\r\n");
|
||||
for (String headerName : response.getHeaderNames())
|
||||
{
|
||||
builder.append(headerName).append(": ");
|
||||
for (Iterator<String> headerValues = response.getHeaders(headerName).iterator(); headerValues.hasNext();)
|
||||
{
|
||||
String headerValue = headerValues.next();
|
||||
if (headerValue != null)
|
||||
builder.append(headerValue);
|
||||
if (headerValues.hasNext())
|
||||
builder.append(",");
|
||||
}
|
||||
builder.append("\r\n");
|
||||
}
|
||||
_log.debug("{} proxying to downstream:{}{}{}{}{}",
|
||||
getRequestId(request),
|
||||
System.lineSeparator(),
|
||||
proxyResponse,
|
||||
System.lineSeparator(),
|
||||
proxyResponse.getHeaders().toString().trim(),
|
||||
System.lineSeparator(),
|
||||
builder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContent(Response proxyResponse, ByteBuffer content)
|
||||
{
|
||||
byte[] buffer;
|
||||
int offset;
|
||||
int length = content.remaining();
|
||||
if (content.hasArray())
|
||||
{
|
||||
buffer = content.array();
|
||||
offset = content.arrayOffset();
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = new byte[length];
|
||||
content.get(buffer);
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
onResponseContent(request, response, proxyResponse, buffer, offset, length);
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
proxyResponse.abort(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response proxyResponse)
|
||||
{
|
||||
onResponseSuccess(request, response, proxyResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Response proxyResponse, Throwable failure)
|
||||
{
|
||||
onResponseFailure(request, response, proxyResponse, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
_log.debug("{} proxying complete", getRequestId(request));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
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;
|
||||
|
||||
public class BalancerServletTest
|
||||
{
|
||||
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
|
||||
public void testRoundRobinBalancer() throws Exception
|
||||
{
|
||||
stickySessions = false;
|
||||
startBalancer(CounterServlet.class);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
byte[] responseBytes = sendRequestToBalancer("/roundRobin");
|
||||
String returnedCounter = readFirstLine(responseBytes);
|
||||
// Counter should increment every other request
|
||||
String expectedCounter = String.valueOf(i / 2);
|
||||
Assert.assertEquals(expectedCounter, returnedCounter);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStickySessionsBalancer() throws Exception
|
||||
{
|
||||
stickySessions = true;
|
||||
startBalancer(CounterServlet.class);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
byte[] responseBytes = sendRequestToBalancer("/stickySessions");
|
||||
String returnedCounter = readFirstLine(responseBytes);
|
||||
// Counter should increment every request
|
||||
String expectedCounter = String.valueOf(i);
|
||||
Assert.assertEquals(expectedCounter, returnedCounter);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyPassReverse() throws Exception
|
||||
{
|
||||
stickySessions = false;
|
||||
startBalancer(RelocationServlet.class);
|
||||
byte[] responseBytes = sendRequestToBalancer("/index.html");
|
||||
String msg = readFirstLine(responseBytes);
|
||||
Assert.assertEquals("success", msg);
|
||||
}
|
||||
|
||||
private String readFirstLine(byte[] responseBytes) throws IOException
|
||||
{
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(responseBytes)));
|
||||
return reader.readLine();
|
||||
}
|
||||
|
||||
public static final class CounterServlet extends HttpServlet
|
||||
{
|
||||
private final AtomicInteger counter = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
// Force session creation
|
||||
req.getSession();
|
||||
resp.setContentType("text/plain");
|
||||
resp.getWriter().print(counter.getAndIncrement());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class RelocationServlet extends HttpServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
if (req.getRequestURI().endsWith("/index.html"))
|
||||
{
|
||||
resp.sendRedirect("http://localhost:" + req.getLocalPort() + req.getContextPath() + req.getServletPath() + "/other.html?secret=pipo+molo");
|
||||
}
|
||||
else
|
||||
{
|
||||
resp.setContentType("text/plain");
|
||||
if ("pipo molo".equals(req.getParameter("secret")))
|
||||
resp.getWriter().println("success");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,41 +16,35 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.proxy;
|
||||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||
import org.eclipse.jetty.proxy.ConnectHandler;
|
||||
import org.eclipse.jetty.server.SelectChannelConnector;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.handler.ConnectHandler;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.proxy.ProxyServlet;
|
||||
|
||||
public class ProxyServer
|
||||
{
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
SelectChannelConnector connector = new SelectChannelConnector(server);
|
||||
ServerConnector connector = new ServerConnector(server);
|
||||
connector.setPort(8888);
|
||||
server.addConnector(connector);
|
||||
|
||||
HandlerCollection handlers = new HandlerCollection();
|
||||
server.setHandler(handlers);
|
||||
|
||||
// Setup proxy servlet
|
||||
ServletContextHandler context = new ServletContextHandler(handlers, "/", ServletContextHandler.SESSIONS);
|
||||
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
|
||||
proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost");
|
||||
proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
|
||||
context.addServlet(proxyServlet, "/*");
|
||||
|
||||
|
||||
// Setup proxy handler to handle CONNECT methods
|
||||
ConnectHandler proxy = new ConnectHandler();
|
||||
proxy.setWhite(new String[]{"mail.google.com"});
|
||||
proxy.addWhite("www.google.com");
|
||||
handlers.addHandler(proxy);
|
||||
// proxy.setWhite(new String[]{"mail.google.com"});
|
||||
// proxy.addWhitelistHost("www.google.com");
|
||||
server.setHandler(proxy);
|
||||
|
||||
// Setup proxy servlet
|
||||
ServletContextHandler context = new ServletContextHandler(proxy, "/", ServletContextHandler.SESSIONS);
|
||||
ServletHolder proxyServlet = new ServletHolder(ProxyServlet.class);
|
||||
// proxyServlet.setInitParameter("whiteList", "google.com, www.eclipse.org, localhost");
|
||||
// proxyServlet.setInitParameter("blackList", "google.com/calendar/*, www.eclipse.org/committers/");
|
||||
context.addServlet(proxyServlet, "/*");
|
||||
|
||||
server.start();
|
||||
}
|
|
@ -0,0 +1,790 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpContentResponse;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.ProxyConfiguration;
|
||||
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.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.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
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.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.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
|
||||
@RunWith(AdvancedRunner.class)
|
||||
public class ProxyServletTest
|
||||
{
|
||||
private static final String PROXIED_HEADER = "X-Proxied";
|
||||
@Rule
|
||||
public final TestTracker tracker = new TestTracker();
|
||||
private HttpClient client;
|
||||
private Server proxy;
|
||||
private ServerConnector proxyConnector;
|
||||
private ProxyServlet proxyServlet;
|
||||
private Server server;
|
||||
private ServerConnector serverConnector;
|
||||
|
||||
private void prepareProxy(ProxyServlet proxyServlet) throws Exception
|
||||
{
|
||||
proxy = new Server();
|
||||
proxyConnector = new ServerConnector(proxy);
|
||||
proxy.addConnector(proxyConnector);
|
||||
|
||||
ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false);
|
||||
this.proxyServlet = proxyServlet;
|
||||
ServletHolder proxyServletHolder = new ServletHolder(proxyServlet);
|
||||
proxyCtx.addServlet(proxyServletHolder, "/*");
|
||||
|
||||
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);
|
||||
appCtx.addServlet(appServletHolder, "/*");
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void disposeProxy() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
proxy.stop();
|
||||
}
|
||||
|
||||
@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
|
||||
{
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.send()
|
||||
.get(5, TimeUnit.SECONDS);
|
||||
Assert.fail();
|
||||
}
|
||||
catch (ExecutionException x)
|
||||
{
|
||||
Assert.assertThat(x.getCause(), Matchers.instanceOf(ConnectException.class));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerDown() throws Exception
|
||||
{
|
||||
prepareProxy(new ProxyServlet());
|
||||
prepareServer(new EmptyHttpServlet());
|
||||
|
||||
// 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
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
PrintWriter writer = resp.getWriter();
|
||||
writer.write(req.getHeader("X-Forwarded-Host"));
|
||||
writer.flush();
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort()).get(5, TimeUnit.SECONDS);
|
||||
Assert.assertThat("Response expected to contain content of X-Forwarded-Host Header from the request",
|
||||
response.getContentAsString(),
|
||||
Matchers.equalTo("localhost:" + serverConnector.getLocalPort()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProxyWhiteList() throws Exception
|
||||
{
|
||||
prepareProxy(new ProxyServlet());
|
||||
prepareServer(new EmptyHttpServlet());
|
||||
int port = serverConnector.getLocalPort();
|
||||
proxyServlet.getWhiteListHosts().add("127.0.0.1:" + 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 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()
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
if (req.getHeader("Via") != null)
|
||||
resp.addHeader(PROXIED_HEADER, "true");
|
||||
}
|
||||
});
|
||||
int port = serverConnector.getLocalPort();
|
||||
client.getProxyConfiguration().getExcludedHosts().add("127.0.0.1:" + port);
|
||||
|
||||
// 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 proxyTo = "http://localhost:" + serverConnector.getLocalPort();
|
||||
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
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
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)
|
||||
{
|
||||
response.setStatus(cachedResponse.getStatus());
|
||||
// Should copy headers too, but keep it simple
|
||||
response.addHeader(cacheHeader, "true");
|
||||
response.getOutputStream().write(cachedResponse.getContent());
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
prepareProxy(proxyServlet);
|
||||
|
||||
// First request
|
||||
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());
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGZIPContentIsProxied() throws Exception
|
||||
{
|
||||
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||
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.addHeader("Content-Encoding", "gzip");
|
||||
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
|
||||
gzipOutputStream.write(content);
|
||||
gzipOutputStream.close();
|
||||
}
|
||||
});
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.servlets.LEVEL=DEBUG
|
|
@ -20,22 +20,20 @@ package org.eclipse.jetty.util;
|
|||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Fast B64 Encoder/Decoder as described in RFC 1421.
|
||||
* <p>Does not insert or interpret whitespace as described in RFC
|
||||
* 1521. If you require this you must pre/post process your data.
|
||||
* <p> Note that in a web context the usual case is to not want
|
||||
* linebreaks or other white space in the encoded output.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class B64Code
|
||||
{
|
||||
// ------------------------------------------------------------------
|
||||
static final char __pad='=';
|
||||
static final char[] __rfc1421alphabet=
|
||||
private static final char __pad='=';
|
||||
private static final char[] __rfc1421alphabet=
|
||||
{
|
||||
'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',
|
||||
|
@ -43,8 +41,7 @@ public class B64Code
|
|||
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
|
||||
};
|
||||
|
||||
static final byte[] __rfc1421nibbles;
|
||||
|
||||
private static final byte[] __rfc1421nibbles;
|
||||
static
|
||||
{
|
||||
__rfc1421nibbles=new byte[256];
|
||||
|
@ -55,26 +52,21 @@ public class B64Code
|
|||
__rfc1421nibbles[(byte)__pad]=0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
private B64Code()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Base 64 encode as described in RFC 1421.
|
||||
* <p>Does not insert whitespace as described in RFC 1521.
|
||||
* @param s String to encode.
|
||||
* @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);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new IllegalArgumentException(e.toString());
|
||||
}
|
||||
return encode(s,null);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
/**
|
||||
* Base 64 encode as described in RFC 1421.
|
||||
* <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.
|
||||
* @return String containing the encoded form of the input.
|
||||
*/
|
||||
static public String encode(String s,String charEncoding)
|
||||
throws UnsupportedEncodingException
|
||||
public static String encode(String s,String charEncoding)
|
||||
{
|
||||
byte[] bytes;
|
||||
if (charEncoding==null)
|
||||
bytes=s.getBytes(StringUtil.__ISO_8859_1);
|
||||
bytes=s.getBytes(Charset.forName(StringUtil.__ISO_8859_1));
|
||||
else
|
||||
bytes=s.getBytes(charEncoding);
|
||||
|
||||
bytes=s.getBytes(Charset.forName(charEncoding));
|
||||
return new String(encode(bytes));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fast Base 64 encode as described in RFC 1421.
|
||||
* <p>Does not insert whitespace as described in RFC 1521.
|
||||
|
@ -103,7 +92,7 @@ public class B64Code
|
|||
* @param b byte array to encode.
|
||||
* @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)
|
||||
return null;
|
||||
|
@ -123,7 +112,7 @@ public class B64Code
|
|||
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
|
||||
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
|
||||
c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
|
||||
c[ci++]=__rfc1421alphabet[b2&077];
|
||||
c[ci++]=__rfc1421alphabet[b2&0x3f];
|
||||
}
|
||||
|
||||
if (bLen!=bi)
|
||||
|
@ -154,8 +143,7 @@ public class B64Code
|
|||
|
||||
return c;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -164,7 +152,7 @@ public class B64Code
|
|||
* @param rfc2045 If true, break lines at 76 characters with CRLF
|
||||
* @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)
|
||||
return null;
|
||||
|
@ -188,7 +176,7 @@ public class B64Code
|
|||
c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
|
||||
c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
|
||||
c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
|
||||
c[ci++]=__rfc1421alphabet[b2&077];
|
||||
c[ci++]=__rfc1421alphabet[b2&0x3f];
|
||||
l+=4;
|
||||
if (l%76==0)
|
||||
{
|
||||
|
@ -228,7 +216,6 @@ public class B64Code
|
|||
return c;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
/**
|
||||
* Base 64 decode as described in RFC 2045.
|
||||
* <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
|
||||
|
@ -236,24 +223,22 @@ public class B64Code
|
|||
* @param charEncoding String representing the character encoding
|
||||
* used to map the decoded bytes into a String.
|
||||
* @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
|
||||
* B64 encoding.
|
||||
*/
|
||||
static public String decode(String encoded,String charEncoding)
|
||||
throws UnsupportedEncodingException
|
||||
public static String decode(String encoded,String charEncoding)
|
||||
{
|
||||
byte[] decoded=decode(encoded);
|
||||
if (charEncoding==null)
|
||||
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.
|
||||
*
|
||||
* <p>Unlike other decode methods, this does not attempt to
|
||||
*
|
||||
* <p>Unlike other decode methods, this does not attempt to
|
||||
* cope with extra whitespace as described in RFC 1521/2045.
|
||||
* <p> Avoids creating extra copies of the input/output.
|
||||
* <p> Note this code has been flattened for performance.
|
||||
|
@ -262,7 +247,7 @@ public class B64Code
|
|||
* @throws IllegalArgumentException if the input is not a valid
|
||||
* B64 encoding.
|
||||
*/
|
||||
static public byte[] decode(char[] b)
|
||||
public static byte[] decode(char[] b)
|
||||
{
|
||||
if (b==null)
|
||||
return null;
|
||||
|
@ -336,8 +321,7 @@ public class B64Code
|
|||
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Base 64 decode as described in RFC 2045.
|
||||
* <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
|
||||
|
@ -346,11 +330,11 @@ public class B64Code
|
|||
* @throws IllegalArgumentException if the input is not a valid
|
||||
* B64 encoding.
|
||||
*/
|
||||
static public byte[] decode(String encoded)
|
||||
public static byte[] decode(String encoded)
|
||||
{
|
||||
if (encoded==null)
|
||||
return null;
|
||||
|
||||
|
||||
int ci=0;
|
||||
byte nibbles[] = new byte[4];
|
||||
int s=0;
|
||||
|
@ -362,7 +346,7 @@ public class B64Code
|
|||
|
||||
if (c==__pad)
|
||||
break;
|
||||
|
||||
|
||||
if (Character.isWhitespace(c))
|
||||
continue;
|
||||
|
||||
|
@ -392,8 +376,7 @@ public class B64Code
|
|||
|
||||
return bout.toByteArray();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
public static void encode(int value,Appendable buf) throws IOException
|
||||
{
|
||||
buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
|
||||
|
@ -404,8 +387,7 @@ public class B64Code
|
|||
buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
|
||||
buf.append('=');
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
public static void encode(long lvalue,Appendable buf) throws IOException
|
||||
{
|
||||
int value=(int)(0xFFFFFFFC&(lvalue>>32));
|
||||
|
@ -414,9 +396,9 @@ public class B64Code
|
|||
buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
|
||||
buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
|
||||
buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
|
||||
|
||||
|
||||
buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
|
||||
|
||||
|
||||
value=0x0FFFFFFF&(int)lvalue;
|
||||
buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
|
||||
buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
|
||||
|
|
|
@ -24,9 +24,13 @@ import java.util.concurrent.RejectedExecutionException;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class TimerScheduler extends AbstractLifeCycle implements Scheduler
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(TimerScheduler.class);
|
||||
|
||||
/*
|
||||
* This class uses the Timer class rather than an ScheduledExecutionService because
|
||||
* it uses the same algorithm internally and the signature is cheaper to use as there are no
|
||||
|
@ -85,7 +89,14 @@ public class TimerScheduler extends AbstractLifeCycle implements Scheduler
|
|||
@Override
|
||||
public void run()
|
||||
{
|
||||
_task.run();
|
||||
try
|
||||
{
|
||||
_task.run();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.debug("Exception while executing task "+_task,x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.util.thread;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -45,7 +46,7 @@ public class SchedulerTest
|
|||
{
|
||||
private static final BenchmarkHelper benchmark = new BenchmarkHelper();
|
||||
private static final Executor executor = Executors.newFixedThreadPool(256);
|
||||
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data()
|
||||
{
|
||||
|
@ -58,27 +59,27 @@ public class SchedulerTest
|
|||
};
|
||||
return Arrays.asList(data);
|
||||
}
|
||||
|
||||
|
||||
// Scheduler _scheduler=new SimpleScheduler();
|
||||
Scheduler _scheduler;
|
||||
|
||||
|
||||
public SchedulerTest(Scheduler scheduler)
|
||||
{
|
||||
_scheduler=scheduler;
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void before() throws Exception
|
||||
{
|
||||
_scheduler.start();
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void after() throws Exception
|
||||
{
|
||||
_scheduler.stop();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExecution() throws Exception
|
||||
{
|
||||
|
@ -92,14 +93,14 @@ public class SchedulerTest
|
|||
executed.set(System.currentTimeMillis());
|
||||
}
|
||||
},3000,TimeUnit.MILLISECONDS);
|
||||
|
||||
|
||||
Thread.sleep(4000);
|
||||
Assert.assertFalse(task.cancel());
|
||||
Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
|
||||
Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTwoExecution() throws Exception
|
||||
{
|
||||
|
@ -113,12 +114,12 @@ public class SchedulerTest
|
|||
executed.set(System.currentTimeMillis());
|
||||
}
|
||||
},3000,TimeUnit.MILLISECONDS);
|
||||
|
||||
|
||||
Thread.sleep(4000);
|
||||
Assert.assertFalse(task.cancel());
|
||||
Assert.assertThat(executed.get(),Matchers.greaterThanOrEqualTo(expected));
|
||||
Assert.assertThat(expected-executed.get(),Matchers.lessThan(1000L));
|
||||
|
||||
|
||||
final AtomicLong executed1 = new AtomicLong();
|
||||
long expected1=System.currentTimeMillis()+3000;
|
||||
Scheduler.Task task1=_scheduler.schedule(new Runnable()
|
||||
|
@ -129,13 +130,13 @@ public class SchedulerTest
|
|||
executed1.set(System.currentTimeMillis());
|
||||
}
|
||||
},3000,TimeUnit.MILLISECONDS);
|
||||
|
||||
|
||||
Thread.sleep(4000);
|
||||
Assert.assertFalse(task1.cancel());
|
||||
Assert.assertThat(executed1.get(),Matchers.greaterThanOrEqualTo(expected1));
|
||||
Assert.assertThat(expected1-executed1.get(),Matchers.lessThan(1000L));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testQuickCancel() throws Exception
|
||||
{
|
||||
|
@ -148,13 +149,13 @@ public class SchedulerTest
|
|||
executed.set(System.currentTimeMillis());
|
||||
}
|
||||
},2000,TimeUnit.MILLISECONDS);
|
||||
|
||||
|
||||
Thread.sleep(100);
|
||||
Assert.assertTrue(task.cancel());
|
||||
Thread.sleep(2500);
|
||||
Assert.assertEquals(0,executed.get());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLongCancel() throws Exception
|
||||
{
|
||||
|
@ -167,20 +168,50 @@ public class SchedulerTest
|
|||
executed.set(System.currentTimeMillis());
|
||||
}
|
||||
},2000,TimeUnit.MILLISECONDS);
|
||||
|
||||
|
||||
Thread.sleep(1600);
|
||||
Assert.assertTrue(task.cancel());
|
||||
Thread.sleep(1000);
|
||||
Assert.assertEquals(0,executed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskThrowsException() throws Exception
|
||||
{
|
||||
long delay = 500;
|
||||
Scheduler.Task task=_scheduler.schedule(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
|
||||
TimeUnit.MILLISECONDS.sleep(2 * delay);
|
||||
|
||||
// Check whether after a task throwing an exception, the scheduler is still working
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
_scheduler.schedule(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
}, delay, TimeUnit.MILLISECONDS);
|
||||
|
||||
Assert.assertTrue(latch.await(2 * delay, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Slow
|
||||
public void testManySchedulesAndCancels() throws Exception
|
||||
{
|
||||
schedule(100,5000,3800,200);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Slow
|
||||
@Ignore
|
||||
|
@ -192,16 +223,16 @@ public class SchedulerTest
|
|||
schedule(2000,30000,2000,50);
|
||||
benchmark.stopStatistics();
|
||||
}
|
||||
|
||||
|
||||
private void schedule(int threads,final int duration, final int delay, final int interval) throws Exception
|
||||
{
|
||||
final Random random = new Random(1);
|
||||
Thread[] test = new Thread[threads];
|
||||
|
||||
Thread[] test = new Thread[threads];
|
||||
|
||||
final AtomicInteger schedules = new AtomicInteger();
|
||||
final SampleStatistic executions = new SampleStatistic();
|
||||
final SampleStatistic cancellations = new SampleStatistic();
|
||||
|
||||
|
||||
for (int i=test.length;i-->0;)
|
||||
{
|
||||
test[i]=new Thread()
|
||||
|
@ -220,7 +251,7 @@ public class SchedulerTest
|
|||
final long expected=now+delay;
|
||||
int cancel=random.nextInt(interval);
|
||||
final boolean expected_to_execute;
|
||||
|
||||
|
||||
last=now+2*interval>end;
|
||||
if (cancel==0 || last)
|
||||
{
|
||||
|
@ -236,15 +267,15 @@ public class SchedulerTest
|
|||
@Override
|
||||
public void run()
|
||||
{
|
||||
long lateness=System.currentTimeMillis()-expected;
|
||||
long lateness=System.currentTimeMillis()-expected;
|
||||
if (expected_to_execute)
|
||||
executions.set(lateness);
|
||||
else
|
||||
executions.set(6666);
|
||||
|
||||
|
||||
}
|
||||
},delay,TimeUnit.MILLISECONDS);
|
||||
|
||||
|
||||
Thread.sleep(cancel);
|
||||
now = System.currentTimeMillis();
|
||||
if (task.cancel())
|
||||
|
@ -255,7 +286,7 @@ public class SchedulerTest
|
|||
else
|
||||
cancellations.set(0);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (!expected_to_execute)
|
||||
{
|
||||
|
@ -270,17 +301,17 @@ public class SchedulerTest
|
|||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
for (Thread thread : test)
|
||||
thread.start();
|
||||
|
||||
|
||||
for (Thread thread : test)
|
||||
thread.join();
|
||||
|
||||
|
||||
// System.err.println(schedules);
|
||||
// System.err.println(executions);
|
||||
// System.err.println(cancellations);
|
||||
|
@ -292,17 +323,17 @@ public class SchedulerTest
|
|||
// All executed or cancelled
|
||||
// Not that SimpleScheduler can execute and cancel an event!
|
||||
Assert.assertThat(0L+schedules.get(),Matchers.lessThanOrEqualTo(executions.getCount()+cancellations.getCount()));
|
||||
|
||||
|
||||
// No really late executions
|
||||
Assert.assertThat(executions.getMax(),Matchers.lessThan(500L));
|
||||
|
||||
// Executions on average are close to the expected time
|
||||
Assert.assertThat(executions.getMean(),Matchers.lessThan(500.0));
|
||||
|
||||
|
||||
// No cancellations long after expected executions
|
||||
Assert.assertThat(cancellations.getMax(),Matchers.lessThan(500L));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
5
pom.xml
5
pom.xml
|
@ -407,6 +407,7 @@
|
|||
<module>jetty-distribution</module>
|
||||
<module>jetty-spring</module>
|
||||
<module>jetty-client</module>
|
||||
<module>jetty-jaspi</module>
|
||||
<module>jetty-osgi</module>
|
||||
<module>examples/embedded</module>
|
||||
<module>examples/async-rest</module>
|
||||
|
@ -420,8 +421,6 @@
|
|||
|
||||
<module>jetty-runner</module>
|
||||
<module>jetty-rhttp</module>
|
||||
<module>jetty-jaspi</module>
|
||||
<module>jetty-proxy</module>
|
||||
<module>jetty-monitor</module>
|
||||
<module>jetty-nested</module>
|
||||
<module>jetty-overlay-deployer</module>
|
||||
|
@ -585,9 +584,7 @@
|
|||
<profile>
|
||||
<id>release</id>
|
||||
<modules>
|
||||
<!--
|
||||
<module>jetty-aggregate</module>
|
||||
-->
|
||||
</modules>
|
||||
</profile>
|
||||
<profile>
|
||||
|
|
Loading…
Reference in New Issue