Merge branch 'master' into javawebsocket-jsr

This commit is contained in:
Joakim Erdfelt 2013-02-19 16:25:29 -07:00
commit f2723404c6
68 changed files with 2251 additions and 801 deletions

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -36,6 +37,7 @@ import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
public class LikeJettyXml
{
@ -53,6 +55,8 @@ public class LikeJettyXml
server.setDumpAfterStart(false);
server.setDumpBeforeStop(false);
server.addBean(new TimerScheduler());
// Setup JMX
MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbContainer);
@ -120,6 +124,12 @@ public class LikeJettyXml
requestLogHandler.setRequestLog(requestLog);
server.setStopAtShutdown(true);
LowResourceMonitor lowResourcesMonitor=new LowResourceMonitor(server);
lowResourcesMonitor.setLowResourcesIdleTimeout(1000);
lowResourcesMonitor.setMaxConnections(2);
lowResourcesMonitor.setPeriod(1200);
server.addBean(lowResourcesMonitor);
server.start();
server.join();

View File

@ -76,17 +76,19 @@ public class ContinueProtocolHandler implements ProtocolHandler
case 100:
{
// All good, continue
conversation.setResponseListener(null);
exchange.resetResponse(true);
conversation.setResponseListeners(listeners);
exchange.proceed(true);
break;
}
default:
{
// Server either does not support 100 Continue, or it does and wants to refuse the request content
// Server either does not support 100 Continue,
// or it does and wants to refuse the request content,
// or we got some other HTTP status code like a redirect.
conversation.setResponseListener(null);
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
notifier.forwardSuccess(listeners, contentResponse);
conversation.setResponseListeners(listeners);
exchange.proceed(false);
break;
}

View File

@ -64,6 +64,7 @@ import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -122,6 +123,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile Executor executor;
private volatile ByteBufferPool byteBufferPool;
private volatile Scheduler scheduler;
private volatile SocketAddressResolver resolver;
private volatile SelectorManager selectorManager;
private volatile HttpField agentField = new HttpField(HttpHeader.USER_AGENT, "Jetty/" + Jetty.VERSION);
private volatile boolean followRedirects = true;
@ -132,6 +134,7 @@ public class HttpClient extends ContainerLifeCycle
private volatile int maxRedirects = 8;
private volatile SocketAddress bindAddress;
private volatile long connectTimeout = 15000;
private volatile long addressResolutionTimeout = 15000;
private volatile long idleTimeout;
private volatile boolean tcpNoDelay = true;
private volatile boolean dispatchIO = true;
@ -198,6 +201,8 @@ public class HttpClient extends ContainerLifeCycle
scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false);
addBean(scheduler);
resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout());
selectorManager = newSelectorManager();
selectorManager.setConnectTimeout(getConnectTimeout());
addBean(selectorManager);
@ -439,10 +444,10 @@ public class HttpClient extends ContainerLifeCycle
*/
public Destination getDestination(String scheme, String host, int port)
{
return provideDestination(scheme, host, port);
return destinationFor(scheme, host, port);
}
protected HttpDestination provideDestination(String scheme, String host, int port)
protected HttpDestination destinationFor(String scheme, String host, int port)
{
port = normalizePort(scheme, port);
@ -480,34 +485,48 @@ public class HttpClient extends ContainerLifeCycle
if (!Arrays.asList("http", "https").contains(scheme))
throw new IllegalArgumentException("Invalid protocol " + scheme);
HttpDestination destination = provideDestination(scheme, request.getHost(), request.getPort());
HttpDestination destination = destinationFor(scheme, request.getHost(), request.getPort());
destination.send(request, listeners);
}
protected void newConnection(HttpDestination destination, Promise<Connection> promise)
protected void newConnection(final HttpDestination destination, final Promise<Connection> promise)
{
SocketChannel channel = null;
try
Destination.Address address = destination.getConnectAddress();
resolver.resolve(address.getHost(), address.getPort(), new Promise<SocketAddress>()
{
channel = SocketChannel.open();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
configure(channel);
channel.configureBlocking(false);
channel.connect(destination.getConnectAddress());
@Override
public void succeeded(SocketAddress socketAddress)
{
SocketChannel channel = null;
try
{
channel = SocketChannel.open();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
configure(channel);
channel.configureBlocking(false);
channel.connect(socketAddress);
Future<Connection> result = new ConnectionCallback(destination, promise);
selectorManager.connect(channel, result);
}
// Must catch all exceptions, since some like
// UnresolvedAddressException are not IOExceptions.
catch (Exception x)
{
if (channel != null)
close(channel);
promise.failed(x);
}
Future<Connection> futureConnection = new ConnectionCallback(destination, promise);
selectorManager.connect(channel, futureConnection);
}
// Must catch all exceptions, since some like
// UnresolvedAddressException are not IOExceptions.
catch (Throwable x)
{
if (channel != null)
close(channel);
promise.failed(x);
}
}
@Override
public void failed(Throwable x)
{
promise.failed(x);
}
});
}
protected void configure(SocketChannel channel) throws SocketException
@ -544,7 +563,7 @@ public class HttpClient extends ContainerLifeCycle
protected void removeConversation(HttpConversation conversation)
{
conversations.remove(conversation.id());
conversations.remove(conversation.getID());
LOG.debug("{} removed", conversation);
}
@ -599,6 +618,22 @@ public class HttpClient extends ContainerLifeCycle
this.connectTimeout = connectTimeout;
}
/**
* @return the timeout, in milliseconds, for the DNS resolution of host addresses
*/
public long getAddressResolutionTimeout()
{
return addressResolutionTimeout;
}
/**
* @param addressResolutionTimeout the timeout, in milliseconds, for the DNS resolution of host addresses
*/
public void setAddressResolutionTimeout(long addressResolutionTimeout)
{
this.addressResolutionTimeout = addressResolutionTimeout;
}
/**
* @return the max time a connection can be idle (that is, without traffic of bytes in either direction)
*/

View File

@ -117,11 +117,15 @@ public class HttpConnection extends AbstractConnection implements Connection
}
if (listener != null)
listeners.add(listener);
send(request, listeners);
HttpConversation conversation = client.getConversation(request.getConversationID(), true);
HttpExchange exchange = new HttpExchange(conversation, getDestination(), request, listeners);
send(exchange);
}
public void send(Request request, List<Response.ResponseListener> listeners)
public void send(HttpExchange exchange)
{
Request request = exchange.getRequest();
normalizeRequest(request);
// Save the old idle timeout to restore it
@ -129,10 +133,8 @@ public class HttpConnection extends AbstractConnection implements Connection
idleTimeout = endPoint.getIdleTimeout();
endPoint.setIdleTimeout(request.getIdleTimeout());
HttpConversation conversation = client.getConversation(request.getConversationID(), true);
HttpExchange exchange = new HttpExchange(conversation, this, request, listeners);
setExchange(exchange);
conversation.getExchanges().offer(exchange);
// Associate the exchange to the connection
associate(exchange);
sender.send(exchange);
}
@ -279,12 +281,21 @@ public class HttpConnection extends AbstractConnection implements Connection
return exchange.get();
}
protected void setExchange(HttpExchange exchange)
protected void associate(HttpExchange exchange)
{
if (!this.exchange.compareAndSet(null, exchange))
throw new UnsupportedOperationException("Pipelined requests not supported");
else
LOG.debug("{} associated to {}", exchange, this);
exchange.setConnection(this);
LOG.debug("{} associated to {}", exchange, this);
}
protected HttpExchange disassociate()
{
HttpExchange exchange = this.exchange.getAndSet(null);
if (exchange != null)
exchange.setConnection(null);
LOG.debug("{} disassociated from {}", exchange, this);
return exchange;
}
@Override
@ -293,7 +304,7 @@ public class HttpConnection extends AbstractConnection implements Connection
HttpExchange exchange = getExchange();
if (exchange != null)
{
exchange.receive();
receive();
}
else
{
@ -310,7 +321,7 @@ public class HttpConnection extends AbstractConnection implements Connection
public void complete(HttpExchange exchange, boolean success)
{
HttpExchange existing = this.exchange.getAndSet(null);
HttpExchange existing = disassociate();
if (existing == exchange)
{
exchange.awaitTermination();
@ -356,13 +367,13 @@ public class HttpConnection extends AbstractConnection implements Connection
}
}
public boolean abort(HttpExchange exchange, Throwable cause)
public boolean abort(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, cause);
return receiver.abort(exchange, cause);
sender.abort(cause);
return receiver.abort(cause);
}
public void proceed(boolean proceed)

View File

@ -18,24 +18,21 @@
package org.eclipse.jetty.client;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
public class HttpConversation implements Attributes
public class HttpConversation extends AttributesMap
{
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
private final Deque<HttpExchange> exchanges = new ConcurrentLinkedDeque<>();
private final HttpClient client;
private final long id;
private volatile List<Response.ResponseListener> listeners;
private volatile Response.ResponseListener listener;
public HttpConversation(HttpClient client, long id)
{
@ -43,7 +40,7 @@ public class HttpConversation implements Attributes
this.id = id;
}
public long id()
public long getID()
{
return id;
}
@ -53,55 +50,112 @@ public class HttpConversation implements Attributes
return exchanges;
}
/**
* Returns the list of response listeners that needs to be notified of response events.
* This list changes as the conversation proceeds, as follows:
* <ol>
* <li>
* request R1 send => conversation.setResponseListener(null)
* <ul>
* <li>exchanges in conversation: E1</li>
* <li>listeners to be notified: E1.listeners</li>
* </ul>
* </li>
* <li>
* response R1 arrived, 401 => conversation.setResponseListener(AuthenticationProtocolHandler.listener)
* <ul>
* <li>exchanges in conversation: E1</li>
* <li>listeners to be notified: AuthenticationProtocolHandler.listener</li>
* </ul>
* </li>
* <li>
* request R2 send => conversation.setResponseListener(null)
* <ul>
* <li>exchanges in conversation: E1 + E2</li>
* <li>listeners to be notified: E2.listeners + E1.listeners</li>
* </ul>
* </li>
* <li>
* response R2 arrived, 302 => conversation.setResponseListener(RedirectProtocolHandler.listener)
* <ul>
* <li>exchanges in conversation: E1 + E2</li>
* <li>listeners to be notified: E2.listeners + RedirectProtocolHandler.listener</li>
* </ul>
* </li>
* <li>
* request R3 send => conversation.setResponseListener(null)
* <ul>
* <li>exchanges in conversation: E1 + E2 + E3</li>
* <li>listeners to be notified: E3.listeners + E1.listeners</li>
* </ul>
* </li>
* <li>
* response R3 arrived, 200 => conversation.setResponseListener(null)
* <ul>
* <li>exchanges in conversation: E1 + E2 + E3</li>
* <li>listeners to be notified: E3.listeners + E1.listeners</li>
* </ul>
* </li>
* </ol>
* Basically the override conversation listener replaces the first exchange response listener,
* and we also notify the last exchange response listeners (if it's not also the first).
*
* This scheme allows for protocol handlers to not worry about other protocol handlers, or to worry
* too much about notifying the first exchange response listeners, but still allowing a protocol
* handler to perform completion activities while another protocol handler performs new ones (as an
* example, the {@link AuthenticationProtocolHandler} stores the successful authentication credentials
* while the {@link RedirectProtocolHandler} performs a redirect).
*
* @return the list of response listeners that needs to be notified of response events
*/
public List<Response.ResponseListener> getResponseListeners()
{
return listeners;
HttpExchange firstExchange = exchanges.peekFirst();
HttpExchange lastExchange = exchanges.peekLast();
if (firstExchange == lastExchange)
{
if (listener != null)
return Arrays.asList(listener);
else
return firstExchange.getResponseListeners();
}
else
{
// Order is important, we want to notify the last exchange first
List<Response.ResponseListener> result = new ArrayList<>(lastExchange.getResponseListeners());
if (listener != null)
result.add(listener);
else
result.addAll(firstExchange.getResponseListeners());
return result;
}
}
public void setResponseListeners(List<Response.ResponseListener> listeners)
/**
* Sets an override response listener that must be notified instead of the first exchange response listeners.
* This works in conjunction with {@link #getResponseListeners()}, returning the appropriate response
* listeners that needs to be notified of response events.
*
* @param listener the override response listener
*/
public void setResponseListener(Response.ResponseListener listener)
{
this.listeners = listeners;
this.listener = listener;
}
public void complete()
{
client.removeConversation(this);
}
@Override
public Object getAttribute(String name)
{
return attributes.get(name);
}
@Override
public void setAttribute(String name, Object attribute)
{
attributes.put(name, attribute);
}
@Override
public void removeAttribute(String name)
{
attributes.remove(name);
}
@Override
public Enumeration<String> getAttributeNames()
{
return Collections.enumeration(attributes.keySet());
}
@Override
public void clearAttributes()
{
attributes.clear();
// The conversation is really terminated only
// when there is no conversation listener that
// may have continued the conversation.
if (listener == null)
client.removeConversation(this);
}
public boolean abort(Throwable cause)
{
HttpExchange exchange = exchanges.peekLast();
return exchange != null && exchange.abort(cause);
return exchange.abort(cause);
}
@Override

View File

@ -19,13 +19,11 @@
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;
import java.util.Queue;
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;
@ -41,7 +39,6 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
@ -56,13 +53,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private final HttpClient client;
private final String scheme;
private final String host;
private final InetSocketAddress address;
private final Queue<RequestContext> requests;
private final Address address;
private final Queue<HttpExchange> exchanges;
private final BlockingQueue<Connection> idleConnections;
private final BlockingQueue<Connection> activeConnections;
private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier;
private final InetSocketAddress proxyAddress;
private final Address proxyAddress;
private final HttpField hostField;
public HttpDestination(HttpClient client, String scheme, String host, int port)
@ -70,11 +67,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
this.client = client;
this.scheme = scheme;
this.host = host;
this.address = new InetSocketAddress(host, port);
this.address = new Address(host, port);
int maxRequestsQueued = client.getMaxRequestsQueuedPerDestination();
int capacity = Math.min(32, maxRequestsQueued);
this.requests = new BlockingArrayQueue<>(capacity, capacity, maxRequestsQueued);
this.exchanges = new BlockingArrayQueue<>(capacity, capacity, maxRequestsQueued);
int maxConnections = client.getMaxConnectionsPerDestination();
capacity = Math.min(8, maxConnections);
@ -86,7 +83,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
ProxyConfiguration proxyConfig = client.getProxyConfiguration();
proxyAddress = proxyConfig != null && proxyConfig.matches(host, port) ?
new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort()) : null;
new Address(proxyConfig.getHost(), proxyConfig.getPort()) : null;
hostField = new HttpField(HttpHeader.HOST, host + ":" + port);
}
@ -101,6 +98,16 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return activeConnections;
}
public RequestNotifier getRequestNotifier()
{
return requestNotifier;
}
public ResponseNotifier getResponseNotifier()
{
return responseNotifier;
}
@Override
public String getScheme()
{
@ -121,7 +128,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
return address.getPort();
}
public InetSocketAddress getConnectAddress()
public Address getConnectAddress()
{
return isProxied() ? proxyAddress : address;
}
@ -146,12 +153,14 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
if (port >= 0 && getPort() != port)
throw new IllegalArgumentException("Invalid request port " + port + " for destination " + this);
RequestContext requestContext = new RequestContext(request, listeners);
HttpConversation conversation = client.getConversation(request.getConversationID(), true);
HttpExchange exchange = new HttpExchange(conversation, this, request, listeners);
if (client.isRunning())
{
if (requests.offer(requestContext))
if (exchanges.offer(exchange))
{
if (!client.isRunning() && requests.remove(requestContext))
if (!client.isRunning() && exchanges.remove(exchange))
{
throw new RejectedExecutionException(client + " is stopping");
}
@ -175,14 +184,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
}
}
public Future<Connection> newConnection()
public void newConnection(Promise<Connection> promise)
{
FuturePromise<Connection> result = new FuturePromise<>();
newConnection(new ProxyPromise(result));
return result;
createConnection(new ProxyPromise(promise));
}
protected void newConnection(Promise<Connection> promise)
protected void createConnection(Promise<Connection> promise)
{
client.newConnection(this, promise);
}
@ -227,7 +234,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
@Override
public void run()
{
drain(x);
abort(x);
}
});
}
@ -236,7 +243,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
// Create a new connection, and pass a ProxyPromise to establish a proxy tunnel, if needed.
// Differently from the case where the connection is created explicitly by applications, here
// we need to do a bit more logging and keep track of the connection count in case of failures.
newConnection(new ProxyPromise(promise)
createConnection(new ProxyPromise(promise)
{
@Override
public void succeeded(Connection connection)
@ -260,18 +267,11 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
}
}
private void drain(Throwable x)
private void abort(Throwable cause)
{
RequestContext requestContext;
while ((requestContext = requests.poll()) != null)
{
Request request = requestContext.request;
requestNotifier.notifyFailure(request, x);
List<Response.ResponseListener> listeners = requestContext.listeners;
HttpResponse response = new HttpResponse(request, listeners);
responseNotifier.notifyFailure(listeners, response, x);
responseNotifier.notifyComplete(listeners, new Result(request, x, response, x));
}
HttpExchange exchange;
while ((exchange = exchanges.poll()) != null)
abort(exchange, cause);
}
/**
@ -282,14 +282,15 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
* <p>If a request is waiting to be executed, it will be dequeued and executed by the new connection.</p>
*
* @param connection the new connection
* @param dispatch whether to dispatch the processing to another thread
*/
protected void process(Connection connection, boolean dispatch)
{
// Ugly cast, but lack of generic reification forces it
final HttpConnection httpConnection = (HttpConnection)connection;
RequestContext requestContext = requests.poll();
if (requestContext == null)
final HttpExchange exchange = exchanges.poll();
if (exchange == null)
{
LOG.debug("{} idle", httpConnection);
if (!idleConnections.offer(httpConnection))
@ -306,13 +307,12 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
}
else
{
final Request request = requestContext.request;
final List<Response.ResponseListener> listeners = requestContext.listeners;
final Request request = exchange.getRequest();
Throwable cause = request.getAbortCause();
if (cause != null)
{
abort(request, listeners, cause);
LOG.debug("Aborted {} before processing", request);
abort(exchange, cause);
LOG.debug("Aborted before processing {}: {}", exchange, cause);
}
else
{
@ -328,13 +328,13 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
@Override
public void run()
{
httpConnection.send(request, listeners);
httpConnection.send(exchange);
}
});
}
else
{
httpConnection.send(request, listeners);
httpConnection.send(exchange);
}
}
}
@ -372,7 +372,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
// We need to execute queued requests even if this connection failed.
// We may create a connection that is not needed, but it will eventually
// idle timeout, so no worries
if (!requests.isEmpty())
if (!exchanges.isEmpty())
{
connection = acquire();
if (connection != null)
@ -391,36 +391,26 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
connection.close();
activeConnections.clear();
drain(new AsynchronousCloseException());
abort(new AsynchronousCloseException());
connectionCount.set(0);
LOG.debug("Closed {}", this);
}
public boolean abort(Request request, Throwable cause)
public boolean remove(HttpExchange exchange)
{
for (RequestContext requestContext : requests)
{
if (requestContext.request == request)
{
if (requests.remove(requestContext))
{
// We were able to remove the pair, so it won't be processed
abort(request, requestContext.listeners, cause);
LOG.debug("Aborted {} while queued", request);
return true;
}
}
}
return false;
return exchanges.remove(exchange);
}
private void abort(Request request, List<Response.ResponseListener> listeners, Throwable cause)
protected void abort(HttpExchange exchange, Throwable cause)
{
HttpResponse response = new HttpResponse(request, listeners);
responseNotifier.notifyFailure(listeners, response, cause);
responseNotifier.notifyComplete(listeners, new Result(request, cause, response, cause));
Request request = exchange.getRequest();
HttpResponse response = exchange.getResponse();
getRequestNotifier().notifyFailure(request, cause);
List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
getResponseNotifier().notifyFailure(listeners, response, cause);
getResponseNotifier().notifyComplete(listeners, new Result(request, cause, response, cause));
}
@Override
@ -432,7 +422,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
@Override
public void dump(Appendable out, String indent) throws IOException
{
ContainerLifeCycle.dumpObject(out, this + " - requests queued: " + requests.size());
ContainerLifeCycle.dumpObject(out, this + " - requests queued: " + exchanges.size());
List<String> connections = new ArrayList<>();
for (Connection connection : idleConnections)
connections.add(connection + " - IDLE");
@ -449,19 +439,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
getScheme(),
getHost(),
getPort(),
proxyAddress == null ? "" : " via " + proxyAddress.getHostString() + ":" + proxyAddress.getPort());
}
private static class RequestContext
{
private final Request request;
private final List<Response.ResponseListener> listeners;
private RequestContext(Request request, List<Response.ResponseListener> listeners)
{
this.request = request;
this.listeners = listeners;
}
proxyAddress == null ? "" : " via " + proxyAddress.getHost() + ":" + proxyAddress.getPort());
}
/**
@ -499,8 +477,8 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private void tunnel(final Connection connection)
{
String target = address.getHostString() + ":" + address.getPort();
Request connect = client.newRequest(proxyAddress.getHostString(), proxyAddress.getPort())
String target = address.getHost() + ":" + address.getPort();
Request connect = client.newRequest(proxyAddress.getHost(), proxyAddress.getPort())
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.CONNECT)
.path(target)

View File

@ -36,21 +36,23 @@ public class HttpExchange
private final AtomicInteger complete = new AtomicInteger();
private final CountDownLatch terminate = new CountDownLatch(2);
private final HttpConversation conversation;
private final HttpConnection connection;
private final HttpDestination destination;
private final Request request;
private final List<Response.ResponseListener> listeners;
private final HttpResponse response;
private volatile boolean last;
private volatile HttpConnection connection;
private volatile Throwable requestFailure;
private volatile Throwable responseFailure;
public HttpExchange(HttpConversation conversation, HttpConnection connection, Request request, List<Response.ResponseListener> listeners)
public HttpExchange(HttpConversation conversation, HttpDestination destination, Request request, List<Response.ResponseListener> listeners)
{
this.conversation = conversation;
this.connection = connection;
this.destination = destination;
this.request = request;
this.listeners = listeners;
this.response = new HttpResponse(request, listeners);
conversation.getExchanges().offer(this);
conversation.setResponseListener(null);
}
public HttpConversation getConversation()
@ -83,25 +85,9 @@ public class HttpExchange
return responseFailure;
}
/**
* @return whether this exchange is the last in the conversation
*/
public boolean isLast()
public void setConnection(HttpConnection connection)
{
return last;
}
/**
* @param last whether this exchange is the last in the conversation
*/
public void setLast(boolean last)
{
this.last = last;
}
public void receive()
{
connection.receive();
this.connection = connection;
}
public AtomicMarkableReference<Result> requestComplete(Throwable failure)
@ -177,8 +163,7 @@ public class HttpExchange
{
// Request and response completed
LOG.debug("{} complete", this);
if (isLast())
conversation.complete();
conversation.complete();
}
result = new Result(getRequest(), getRequestFailure(), getResponse(), getResponseFailure());
}
@ -188,10 +173,23 @@ public class HttpExchange
public boolean abort(Throwable cause)
{
LOG.debug("Aborting {} reason {}", this, cause);
boolean aborted = connection.abort(this, cause);
LOG.debug("Aborted {}: {}", this, aborted);
return aborted;
if (destination.remove(this))
{
destination.abort(this, cause);
LOG.debug("Aborted while queued {}: {}", this, cause);
return true;
}
else
{
HttpConnection connection = this.connection;
// If there is no connection, this exchange is already completed
if (connection == null)
return false;
boolean aborted = connection.abort(cause);
LOG.debug("Aborted while active ({}) {}: {}", aborted, this, cause);
return aborted;
}
}
public void resetResponse(boolean success)
@ -204,7 +202,9 @@ public class HttpExchange
public void proceed(boolean proceed)
{
connection.proceed(proceed);
HttpConnection connection = this.connection;
if (connection != null)
connection.proceed(proceed);
}
public void terminateRequest()

View File

@ -22,7 +22,6 @@ import java.io.EOFException;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
@ -52,13 +51,11 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
private final HttpParser parser = new HttpParser(this);
private final HttpConnection connection;
private final ResponseNotifier responseNotifier;
private ContentDecoder decoder;
public HttpReceiver(HttpConnection connection)
{
this.connection = connection;
this.responseNotifier = new ResponseNotifier(connection.getHttpClient());
}
public void receive()
@ -146,41 +143,14 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
response.version(version).status(status).reason(reason);
// Probe the protocol handlers
HttpExchange initialExchange = conversation.getExchanges().peekFirst();
HttpClient client = connection.getHttpClient();
ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.getRequest(), response);
Response.Listener handlerListener = protocolHandler == null ? null : protocolHandler.getResponseListener();
if (handlerListener == null)
{
exchange.setLast(true);
if (initialExchange == exchange)
{
conversation.setResponseListeners(exchange.getResponseListeners());
}
else
{
List<Response.ResponseListener> listeners = new ArrayList<>(exchange.getResponseListeners());
listeners.addAll(initialExchange.getResponseListeners());
conversation.setResponseListeners(listeners);
}
}
else
{
LOG.debug("Found protocol handler {}", protocolHandler);
if (initialExchange == exchange)
{
conversation.setResponseListeners(Collections.<Response.ResponseListener>singletonList(handlerListener));
}
else
{
List<Response.ResponseListener> listeners = new ArrayList<>(exchange.getResponseListeners());
listeners.add(handlerListener);
conversation.setResponseListeners(listeners);
}
}
exchange.getConversation().setResponseListener(handlerListener);
LOG.debug("Receiving {}", response);
responseNotifier.notifyBegin(conversation.getResponseListeners(), response);
ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
notifier.notifyBegin(conversation.getResponseListeners(), response);
}
}
return false;
@ -197,7 +167,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{
HttpConversation conversation = exchange.getConversation();
HttpResponse response = exchange.getResponse();
boolean process = responseNotifier.notifyHeader(conversation.getResponseListeners(), response, field);
ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
boolean process = notifier.notifyHeader(conversation.getResponseListeners(), response, field);
if (process)
{
response.getHeaders().add(field);
@ -250,7 +221,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
HttpConversation conversation = exchange.getConversation();
HttpResponse response = exchange.getResponse();
LOG.debug("Headers {}", response);
responseNotifier.notifyHeaders(conversation.getResponseListeners(), response);
ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
notifier.notifyHeaders(conversation.getResponseListeners(), response);
Enumeration<String> contentEncodings = response.getHeaders().getValues(HttpHeader.CONTENT_ENCODING.asString(), ",");
if (contentEncodings != null)
@ -292,7 +264,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
LOG.debug("{} {}: {} bytes", decoder, response, buffer.remaining());
}
responseNotifier.notifyContent(conversation.getResponseListeners(), response, buffer);
ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
notifier.notifyContent(conversation.getResponseListeners(), response, buffer);
}
}
return false;
@ -326,15 +299,15 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
HttpResponse response = exchange.getResponse();
List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
responseNotifier.notifySuccess(listeners, response);
ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
notifier.notifySuccess(listeners, response);
LOG.debug("Received {}", response);
Result result = completion.getReference();
if (result != null)
{
connection.complete(exchange, !result.isFailed());
responseNotifier.notifyComplete(listeners, result);
notifier.notifyComplete(listeners, result);
}
return true;
@ -368,7 +341,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
HttpResponse response = exchange.getResponse();
HttpConversation conversation = exchange.getConversation();
responseNotifier.notifyFailure(conversation.getResponseListeners(), response, failure);
ResponseNotifier notifier = connection.getDestination().getResponseNotifier();
notifier.notifyFailure(conversation.getResponseListeners(), response, failure);
LOG.debug("Failed {} {}", response, failure);
Result result = completion.getReference();
@ -376,7 +350,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{
connection.complete(exchange, false);
responseNotifier.notifyComplete(conversation.getResponseListeners(), result);
notifier.notifyComplete(conversation.getResponseListeners(), result);
}
return true;
@ -411,7 +385,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
fail(new TimeoutException());
}
public boolean abort(HttpExchange exchange, Throwable cause)
public boolean abort(Throwable cause)
{
return fail(cause);
}

View File

@ -82,7 +82,7 @@ public class HttpRequest implements Request
scheme = uri.getScheme();
host = uri.getHost();
port = client.normalizePort(scheme, uri.getPort());
path = uri.getPath();
path = uri.getRawPath();
String query = uri.getRawQuery();
if (query != null)
{
@ -468,8 +468,7 @@ public class HttpRequest implements Request
public boolean abort(Throwable cause)
{
aborted = Objects.requireNonNull(cause);
if (client.provideDestination(getScheme(), getHost(), getPort()).abort(this, cause))
return true;
// The conversation may be null if it is already completed
HttpConversation conversation = client.getConversation(getConversationID(), false);
return conversation != null && conversation.abort(cause);
}

View File

@ -19,10 +19,8 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicMarkableReference;
@ -30,7 +28,6 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.ContentProvider;
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.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
@ -51,16 +48,12 @@ public class HttpSender implements AsyncContentProvider.Listener
private final AtomicReference<SendState> sendState = new AtomicReference<>(SendState.IDLE);
private final HttpGenerator generator = new HttpGenerator();
private final HttpConnection connection;
private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier;
private Iterator<ByteBuffer> contentIterator;
private ContinueContentChunk continueContentChunk;
public HttpSender(HttpConnection connection)
{
this.connection = connection;
this.requestNotifier = new RequestNotifier(connection.getHttpClient());
this.responseNotifier = new ResponseNotifier(connection.getHttpClient());
}
@Override
@ -108,20 +101,6 @@ public class HttpSender implements AsyncContentProvider.Listener
if (!updateState(State.IDLE, State.BEGIN))
throw new IllegalStateException();
// Arrange the listeners, so that if there is a request failure the proper listeners are notified
HttpConversation conversation = exchange.getConversation();
HttpExchange initialExchange = conversation.getExchanges().peekFirst();
if (initialExchange == exchange)
{
conversation.setResponseListeners(exchange.getResponseListeners());
}
else
{
List<Response.ResponseListener> listeners = new ArrayList<>(exchange.getResponseListeners());
listeners.addAll(initialExchange.getResponseListeners());
conversation.setResponseListeners(listeners);
}
Request request = exchange.getRequest();
Throwable cause = request.getAbortCause();
if (cause != null)
@ -131,7 +110,8 @@ public class HttpSender implements AsyncContentProvider.Listener
else
{
LOG.debug("Sending {}", request);
requestNotifier.notifyBegin(request);
RequestNotifier notifier = connection.getDestination().getRequestNotifier();
notifier.notifyBegin(request);
ContentProvider content = request.getContent();
this.contentIterator = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
@ -214,7 +194,8 @@ public class HttpSender implements AsyncContentProvider.Listener
{
if (!updateState(currentState, State.HEADERS))
continue;
requestNotifier.notifyHeaders(request);
RequestNotifier notifier = connection.getDestination().getRequestNotifier();
notifier.notifyHeaders(request);
break out;
}
case HEADERS:
@ -461,7 +442,8 @@ public class HttpSender implements AsyncContentProvider.Listener
if (!updateState(current, State.COMMIT))
continue;
LOG.debug("Committed {}", request);
requestNotifier.notifyCommit(request);
RequestNotifier notifier = connection.getDestination().getRequestNotifier();
notifier.notifyCommit(request);
return true;
case COMMIT:
if (!updateState(current, State.COMMIT))
@ -495,8 +477,9 @@ public class HttpSender implements AsyncContentProvider.Listener
// It is important to notify completion *after* we reset because
// the notification may trigger another request/response
HttpDestination destination = connection.getDestination();
Request request = exchange.getRequest();
requestNotifier.notifySuccess(request);
destination.getRequestNotifier().notifySuccess(request);
LOG.debug("Sent {}", request);
Result result = completion.getReference();
@ -505,7 +488,7 @@ public class HttpSender implements AsyncContentProvider.Listener
connection.complete(exchange, !result.isFailed());
HttpConversation conversation = exchange.getConversation();
responseNotifier.notifyComplete(conversation.getResponseListeners(), result);
destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
}
return true;
@ -533,8 +516,9 @@ public class HttpSender implements AsyncContentProvider.Listener
exchange.terminateRequest();
HttpDestination destination = connection.getDestination();
Request request = exchange.getRequest();
requestNotifier.notifyFailure(request, failure);
destination.getRequestNotifier().notifyFailure(request, failure);
LOG.debug("Failed {} {}", request, failure);
Result result = completion.getReference();
@ -551,13 +535,13 @@ public class HttpSender implements AsyncContentProvider.Listener
connection.complete(exchange, false);
HttpConversation conversation = exchange.getConversation();
responseNotifier.notifyComplete(conversation.getResponseListeners(), result);
destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
}
return true;
}
public boolean abort(HttpExchange exchange, Throwable cause)
public boolean abort(Throwable cause)
{
State current = state.get();
boolean abortable = isBeforeCommit(current) ||

View File

@ -19,7 +19,10 @@
package org.eclipse.jetty.client;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
@ -28,6 +31,14 @@ import org.eclipse.jetty.http.HttpMethod;
public class RedirectProtocolHandler extends Response.Listener.Empty implements ProtocolHandler
{
private static String SCHEME_REGEXP = "(^https?)";
private static String AUTHORITY_REGEXP = "([^/\\?#]+)";
// The location may be relative so the scheme://authority part may be missing
private static String DESTINATION_REGEXP = "(" + SCHEME_REGEXP + "://" + AUTHORITY_REGEXP + ")?";
private static String PATH_REGEXP = "([^\\?#]*)";
private static String QUERY_REGEXP = "([^#]*)";
private static String FRAGMENT_REGEXP = "(.*)";
private static Pattern URI_PATTERN = Pattern.compile(DESTINATION_REGEXP + PATH_REGEXP + QUERY_REGEXP + FRAGMENT_REGEXP);
private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirects";
private final HttpClient client;
@ -66,37 +77,55 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
Request request = result.getRequest();
Response response = result.getResponse();
URI location = URI.create(response.getHeaders().get("location"));
int status = response.getStatus();
switch (status)
String location = response.getHeaders().get("location");
if (location != null)
{
case 301:
URI newURI = sanitize(location);
if (newURI != null)
{
if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD)
redirect(result, request.getMethod(), location);
else
fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET or HEAD request", response));
break;
if (!newURI.isAbsolute())
newURI = request.getURI().resolve(newURI);
int status = response.getStatus();
switch (status)
{
case 301:
{
if (request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD)
redirect(result, request.getMethod(), newURI);
else
fail(result, new HttpResponseException("HTTP protocol violation: received 301 for non GET or HEAD request", response));
break;
}
case 302:
case 303:
{
// Redirect must be done using GET
redirect(result, HttpMethod.GET, newURI);
break;
}
case 307:
{
// Keep same method
redirect(result, request.getMethod(), newURI);
break;
}
default:
{
fail(result, new HttpResponseException("Unhandled HTTP status code " + status, response));
break;
}
}
}
case 302:
case 303:
else
{
// Redirect must be done using GET
redirect(result, HttpMethod.GET, location);
break;
}
case 307:
{
// Keep same method
redirect(result, request.getMethod(), location);
break;
}
default:
{
fail(result, new HttpResponseException("Unhandled HTTP status code " + status, response));
break;
fail(result, new HttpResponseException("Malformed Location header " + location, response));
}
}
else
{
fail(result, new HttpResponseException("Missing Location header " + location, response));
}
}
else
{
@ -104,6 +133,43 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
}
}
private URI sanitize(String location)
{
// Redirects should be valid, absolute, URIs, with properly escaped paths and encoded
// query parameters. However, shit happens, and here we try our best to recover.
try
{
// Direct hit first: if passes, we're good
return new URI(location);
}
catch (URISyntaxException x)
{
Matcher matcher = URI_PATTERN.matcher(location);
if (matcher.matches())
{
String scheme = matcher.group(2);
String authority = matcher.group(3);
String path = matcher.group(4);
String query = matcher.group(5);
if (query.length() == 0)
query = null;
String fragment = matcher.group(6);
if (fragment.length() == 0)
fragment = null;
try
{
return new URI(scheme, authority, path, query, fragment);
}
catch (URISyntaxException xx)
{
// Give up
}
}
return null;
}
}
private void redirect(Result result, HttpMethod method, URI location)
{
final Request request = result.getRequest();
@ -147,7 +213,6 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
Response response = result.getResponse();
HttpConversation conversation = client.getConversation(request.getConversationID(), false);
List<Response.ResponseListener> listeners = conversation.getExchanges().peekFirst().getResponseListeners();
// TODO: should we replay all events, or just the failure ?
notifier.notifyFailure(listeners, response, failure);
notifier.notifyComplete(listeners, new Result(request, response, failure));
}

View File

@ -18,13 +18,15 @@
package org.eclipse.jetty.client.api;
import org.eclipse.jetty.util.Promise;
/**
* {@link Connection} represent a connection to a {@link Destination} and allow applications to send
* requests via {@link #send(Request, Response.CompleteListener)}.
* <p />
* {@link Connection}s are normally pooled by {@link Destination}s, but unpooled {@link Connection}s
* may be created by applications that want to do their own connection management via
* {@link Destination#newConnection()} and {@link Connection#close()}.
* {@link Destination#newConnection(Promise)} and {@link Connection#close()}.
*/
public interface Connection extends AutoCloseable
{

View File

@ -18,16 +18,16 @@
package org.eclipse.jetty.client.api;
import java.util.concurrent.Future;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Promise;
/**
* {@link Destination} represents the triple made of the {@link #getScheme}, the {@link #getHost}
* and the {@link #getPort}.
* <p />
* {@link Destination} holds a pool of {@link Connection}s, but allows to create unpooled
* connections if the application wants full control over connection management via {@link #newConnection()}.
* connections if the application wants full control over connection management via {@link #newConnection(Promise)}.
* <p />
* {@link Destination}s may be obtained via {@link HttpClient#getDestination(String, String, int)}
*/
@ -49,7 +49,40 @@ public interface Destination
int getPort();
/**
* @return a future to a new, unpooled, {@link Connection}
* Creates asynchronously a new, unpooled, {@link Connection} that will be returned
* at a later time through the given {@link Promise}.
* <p />
* Use {@link FuturePromise} to wait for the connection:
* <pre>
* Destination destination = ...;
* FuturePromise&lt;Connection&gt; futureConnection = new FuturePromise&lt;&gt;();
* destination.newConnection(futureConnection);
* Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
* </pre>
*
* @param promise the promise of a new, unpooled, {@link Connection}
*/
Future<Connection> newConnection();
void newConnection(Promise<Connection> promise);
public static class Address
{
private final String host;
private final int port;
public Address(String host, int port)
{
this.host = host;
this.port = port;
}
public String getHost()
{
return host;
}
public int getPort()
{
return port;
}
}
}

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
@ -43,7 +44,9 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
start(new EmptyServerHandler());
Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
try (Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS))
FuturePromise<Connection> futureConnection = new FuturePromise<>();
destination.newConnection(futureConnection);
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
{
Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scheme);
FutureResponseListener listener = new FutureResponseListener(request);
@ -66,7 +69,9 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
start(new EmptyServerHandler());
Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS);
FuturePromise<Connection> futureConnection = new FuturePromise<>();
destination.newConnection(futureConnection);
Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
Request request = client.newRequest(destination.getHost(), destination.getPort()).scheme(scheme);
FutureResponseListener listener = new FutureResponseListener(request);
connection.send(request, listener);

View File

@ -18,13 +18,12 @@
package org.eclipse.jetty.client;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -38,10 +37,13 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.fail;
public class HttpClientRedirectTest extends AbstractHttpClientServerTest
{
public HttpClientRedirectTest(SslContextFactory sslContextFactory)
@ -199,6 +201,62 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
Assert.assertTrue(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
}
@Test
public void testRelativeLocation() throws Exception
{
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/done?relative=true")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
}
@Test
public void testAbsoluteURIPathWithSpaces() throws Exception
{
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/a+space?decode=true")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
}
@Test
public void testRelativeURIPathWithSpaces() throws Exception
{
Response response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/localhost/a+space?relative=true&decode=true")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertFalse(response.getHeaders().containsKey(HttpHeader.LOCATION.asString()));
}
@Test
public void testRedirectFailed() throws Exception
{
try
{
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/303/doesNotExist/done")
.timeout(5, TimeUnit.SECONDS)
.send();
}
catch (ExecutionException x)
{
Assert.assertThat(x.getCause(), Matchers.instanceOf(UnresolvedAddressException.class));
}
}
private class RedirectHandler extends AbstractHandler
{
@Override
@ -212,10 +270,17 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
response.setStatus(status);
String host = paths[2];
response.setHeader("Location", request.getScheme() + "://" + host + ":" + request.getServerPort() + "/" + paths[3]);
String path = paths[3];
boolean relative = Boolean.parseBoolean(request.getParameter("relative"));
String location = relative ? "" : request.getScheme() + "://" + host + ":" + request.getServerPort();
location += "/" + path;
String close = request.getParameter("close");
if (Boolean.parseBoolean(close))
if (Boolean.parseBoolean(request.getParameter("decode")))
location = URLDecoder.decode(location, "UTF-8");
response.setHeader("Location", location);
if (Boolean.parseBoolean(request.getParameter("close")))
response.setHeader("Connection", "close");
}
catch (NumberFormatException x)

View File

@ -40,6 +40,7 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.hamcrest.Matchers;
@ -175,7 +176,9 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
final CountDownLatch latch = new CountDownLatch(1);
Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
try (Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS))
FuturePromise<Connection> futureConnection = new FuturePromise<>();
destination.newConnection(futureConnection);
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
{
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
@ -203,7 +206,9 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
final CountDownLatch latch = new CountDownLatch(1);
Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
try (Connection connection = destination.newConnection().get(5, TimeUnit.SECONDS))
FuturePromise<Connection> futureConnection = new FuturePromise<>();
destination.newConnection(futureConnection);
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
{
Request request = client.newRequest(destination.getHost(), destination.getPort())
.scheme(scheme)

View File

@ -74,9 +74,9 @@ public class HttpReceiverTest
{
HttpRequest request = new HttpRequest(client, URI.create("http://localhost"));
FutureResponseListener listener = new FutureResponseListener(request);
HttpExchange exchange = new HttpExchange(conversation, connection, request, Collections.<Response.ResponseListener>singletonList(listener));
HttpExchange exchange = new HttpExchange(conversation, destination, request, Collections.<Response.ResponseListener>singletonList(listener));
conversation.getExchanges().offer(exchange);
connection.setExchange(exchange);
connection.associate(exchange);
exchange.requestComplete(null);
exchange.terminateRequest();
return exchange;
@ -91,7 +91,7 @@ public class HttpReceiverTest
"\r\n");
HttpExchange exchange = newExchange();
FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
connection.receive();
Response response = listener.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
@ -115,7 +115,7 @@ public class HttpReceiverTest
content);
HttpExchange exchange = newExchange();
FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
connection.receive();
Response response = listener.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
@ -142,9 +142,9 @@ public class HttpReceiverTest
content1);
HttpExchange exchange = newExchange();
FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
connection.receive();
endPoint.setInputEOF();
exchange.receive();
connection.receive();
try
{
@ -166,7 +166,7 @@ public class HttpReceiverTest
"\r\n");
HttpExchange exchange = newExchange();
FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
connection.receive();
// Simulate an idle timeout
connection.idleTimeout();
@ -190,7 +190,7 @@ public class HttpReceiverTest
"\r\n");
HttpExchange exchange = newExchange();
FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
connection.receive();
try
{
@ -221,20 +221,20 @@ public class HttpReceiverTest
"\r\n");
HttpExchange exchange = newExchange();
FutureResponseListener listener = (FutureResponseListener)exchange.getResponseListeners().get(0);
exchange.receive();
connection.receive();
endPoint.reset();
ByteBuffer buffer = ByteBuffer.wrap(gzip);
int fragment = buffer.limit() - 1;
buffer.limit(fragment);
endPoint.setInput(buffer);
exchange.receive();
connection.receive();
endPoint.reset();
buffer.limit(gzip.length);
buffer.position(fragment);
endPoint.setInput(buffer);
exchange.receive();
connection.receive();
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);

View File

@ -39,6 +39,7 @@ import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.client.util.OutputStreamContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.FuturePromise;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@ -149,7 +150,9 @@ public class Usage
client.start();
// Create an explicit connection, and use try-with-resources to manage it
try (Connection connection = client.getDestination("http", "localhost", 8080).newConnection().get(5, TimeUnit.SECONDS))
FuturePromise<Connection> futureConnection = new FuturePromise<>();
client.getDestination("http", "localhost", 8080).newConnection(futureConnection);
try (Connection connection = futureConnection.get(5, TimeUnit.SECONDS))
{
Request request = client.newRequest("localhost", 8080);

View File

@ -200,4 +200,5 @@ etc/jetty-requestlog.xml
# etc/jetty-stats.xml
# etc/jetty-debug.xml
# etc/jetty-ipaccess.xml
# etc/jetty-lowresources.xml
#===========================================================

View File

@ -51,9 +51,11 @@ import org.eclipse.jetty.util.log.Logger;
* specific to a webapp).
*
* The context selected is based on classloaders. First
* we try looking in at the classloader that is associated
* with the current webapp context (if there is one). If
* not, we use the thread context classloader.
* we try looking at the thread context classloader if it is set, and walk its
* hierarchy, creating a context if none is found. If the thread context classloader
* is not set, then we use the classloader associated with the current Context.
*
* If there is no current context, or no classloader, we return null.
*
* Created: Fri Jun 27 09:26:40 2003
*
@ -79,9 +81,16 @@ public class ContextFactory implements ObjectFactory
/**
* Find or create a context which pertains to a classloader.
*
* We use either the classloader for the current ContextHandler if
* we are handling a request, OR we use the thread context classloader
* if we are not processing a request.
* If the thread context classloader is set, we try to find an already-created naming context
* for it. If one does not exist, we walk its classloader hierarchy until one is found, or we
* run out of parent classloaders. In the latter case, we will create a new naming context associated
* with the original thread context classloader.
*
* If the thread context classloader is not set, we obtain the classloader from the current
* jetty Context, and look for an already-created naming context.
*
* If there is no current jetty Context, or it has no associated classloader, we
* return null.
* @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context, java.util.Hashtable)
*/
public Object getObjectInstance (Object obj,
@ -98,41 +107,89 @@ public class ContextFactory implements ObjectFactory
return ctx;
}
ClassLoader loader = null;
loader = Thread.currentThread().getContextClassLoader();
if (__log.isDebugEnabled() && loader != null) __log.debug("Using thread context classloader");
if (loader == null && ContextHandler.getCurrentContext() != null)
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
ClassLoader loader = tccl;
//If the thread context classloader is set, then try its hierarchy to find a matching context
if (loader != null)
{
if (__log.isDebugEnabled() && loader != null) __log.debug("Trying thread context classloader");
while (ctx == null && loader != null)
{
ctx = getContextForClassLoader(loader);
if (ctx == null && loader != null)
loader = loader.getParent();
}
if (ctx == null)
{
ctx = newNamingContext(obj, tccl, env, name, nameCtx);
__contextMap.put (tccl, ctx);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+tccl);
}
return ctx;
}
//If trying thread context classloader hierarchy failed, try the
//classloader associated with the current context
if (ContextHandler.getCurrentContext() != null)
{
if (__log.isDebugEnabled() && loader != null) __log.debug("Trying classloader of current org.eclipse.jetty.server.handler.ContextHandler");
loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader();
if (__log.isDebugEnabled() && loader != null) __log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler");
ctx = (Context)__contextMap.get(loader);
if (ctx == null && loader != null)
{
ctx = newNamingContext(obj, loader, env, name, nameCtx);
__contextMap.put (loader, ctx);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
}
return ctx;
}
return null;
}
//Get the context matching the classloader
ctx = (Context)__contextMap.get(loader);
//The map does not contain an entry for this classloader
if (ctx == null)
{
//Didn't find a context to match, make one
Reference ref = (Reference)obj;
StringRefAddr parserAddr = (StringRefAddr)ref.get("parser");
String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent());
NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance());
/**
* Create a new NamingContext.
* @param obj
* @param loader
* @param env
* @param name
* @param parentCtx
* @return
* @throws Exception
*/
public NamingContext newNamingContext(Object obj, ClassLoader loader, Hashtable env, Name name, Context parentCtx)
throws Exception
{
Reference ref = (Reference)obj;
StringRefAddr parserAddr = (StringRefAddr)ref.get("parser");
String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent());
NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance());
ctx = new NamingContext (env,
name.get(0),
(NamingContext)nameCtx,
parser);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
__contextMap.put (loader, ctx);
}
return ctx;
return new NamingContext (env,
name.get(0),
(NamingContext)parentCtx,
parser);
}
/**
* Find the naming Context for the given classloader
* @param loader
* @return
*/
public Context getContextForClassLoader(ClassLoader loader)
{
if (loader == null)
return null;
return (Context)__contextMap.get(loader);
}
/**

View File

@ -41,10 +41,14 @@ import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.eclipse.jetty.jndi.NamingContext;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.Ignore;
import org.junit.Test;
/**
*
@ -68,70 +72,138 @@ public class TestJNDI
}
}
@Test
public void testIt() throws Exception
public void testThreadContextClassloaderAndCurrentContext()
throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
//create a jetty context, and start it so that its classloader it created
//and it is the current context
ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
ContextHandler ch = new ContextHandler();
URLClassLoader chLoader = new URLClassLoader(new URL[0], currentLoader);
ch.setClassLoader(chLoader);
//Create another one
ContextHandler ch2 = new ContextHandler();
URLClassLoader ch2Loader = new URLClassLoader(new URL[0], currentLoader);
ch2.setClassLoader(ch2Loader);
try
{
//Uncomment to aid with debug
/*
javaRootURLContext.getRoot().addListener(new NamingContext.Listener()
ch.setContextPath("/ch");
ch.addEventListener(new ServletContextListener()
{
public void unbind(NamingContext ctx, Binding binding)
private Context comp;
private Object testObj = new Object();
public void contextInitialized(ServletContextEvent sce)
{
System.err.println("java unbind "+binding+" from "+ctx.getName());
try
{
InitialContext initCtx = new InitialContext();
Context java = (Context)initCtx.lookup("java:");
assertNotNull(java);
comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
Context env = ((Context)comp).createSubcontext("env");
assertNotNull(env);
env.bind("ch", testObj);
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
public Binding bind(NamingContext ctx, Binding binding)
public void contextDestroyed(ServletContextEvent sce)
{
System.err.println("java bind "+binding+" to "+ctx.getName());
return binding;
try
{
assertNotNull(comp);
assertEquals(testObj,comp.lookup("env/ch"));
comp.destroySubcontext("env");
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
});
//Starting the context makes it current and creates a classloader for it
ch.start();
localContextRoot.getRoot().addListener(new NamingContext.Listener()
ch2.setContextPath("/ch2");
ch2.addEventListener(new ServletContextListener()
{
public void unbind(NamingContext ctx, Binding binding)
{
System.err.println("local unbind "+binding+" from "+ctx.getName());
}
private Context comp;
private Object testObj = new Object();
public Binding bind(NamingContext ctx, Binding binding)
public void contextInitialized(ServletContextEvent sce)
{
System.err.println("local bind "+binding+" to "+ctx.getName());
return binding;
try
{
InitialContext initCtx = new InitialContext();
comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
//another context's bindings should not be visible
Context env = ((Context)comp).createSubcontext("env");
try
{
env.lookup("ch");
fail("java:comp/env visible from another context!");
}
catch (NameNotFoundException e)
{
//expected
}
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
public void contextDestroyed(ServletContextEvent sce)
{
try
{
assertNotNull(comp);
comp.destroySubcontext("env");
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
});
*/
//make the new context the current one
ch2.start();
}
finally
{
ch.stop();
ch2.stop();
Thread.currentThread().setContextClassLoader(currentLoader);
}
}
@Test
public void testJavaNameParsing() throws Exception
{
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
//set the current thread's classloader
currentThread.setContextClassLoader(childLoader1);
//set the current thread's classloader
currentThread.setContextClassLoader(childLoader1);
InitialContext initCtxA = new InitialContext();
initCtxA.bind ("blah", "123");
assertEquals ("123", initCtxA.lookup("blah"));
initCtxA.destroySubcontext("blah");
try
{
initCtxA.lookup("blah");
fail("context blah was not destroyed");
}
catch (NameNotFoundException e)
{
//expected
}
try
{
InitialContext initCtx = new InitialContext();
Context sub0 = (Context)initCtx.lookup("java:");
@ -181,10 +253,74 @@ public class TestJNDI
Context fee = ncontext.createSubcontext("fee");
fee.bind ("fi", "88");
assertEquals("88", initCtxA.lookup("java:/fee/fi"));
assertEquals("88", initCtxA.lookup("java:/fee/fi/"));
assertTrue (initCtxA.lookup("java:/fee/") instanceof javax.naming.Context);
assertEquals("88", initCtx.lookup("java:/fee/fi"));
assertEquals("88", initCtx.lookup("java:/fee/fi/"));
assertTrue (initCtx.lookup("java:/fee/") instanceof javax.naming.Context);
}
finally
{
InitialContext ic = new InitialContext();
Context java = (Context)ic.lookup("java:");
java.destroySubcontext("fee");
currentThread.setContextClassLoader(currentLoader);
}
}
@Test
public void testIt() throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
try
{
//Uncomment to aid with debug
/*
javaRootURLContext.getRoot().addListener(new NamingContext.Listener()
{
public void unbind(NamingContext ctx, Binding binding)
{
System.err.println("java unbind "+binding+" from "+ctx.getName());
}
public Binding bind(NamingContext ctx, Binding binding)
{
System.err.println("java bind "+binding+" to "+ctx.getName());
return binding;
}
});
localContextRoot.getRoot().addListener(new NamingContext.Listener()
{
public void unbind(NamingContext ctx, Binding binding)
{
System.err.println("local unbind "+binding+" from "+ctx.getName());
}
public Binding bind(NamingContext ctx, Binding binding)
{
System.err.println("local bind "+binding+" to "+ctx.getName());
return binding;
}
});
*/
//Set up the tccl before doing any jndi operations
currentThread.setContextClassLoader(childLoader1);
InitialContext initCtx = new InitialContext();
//Test we can lookup the root java: naming tree
Context sub0 = (Context)initCtx.lookup("java:");
assertNotNull(sub0);
//Test that we cannot bind java:comp as it should
//already be bound
try
{
Context sub1 = sub0.createSubcontext ("comp");
@ -197,8 +333,10 @@ public class TestJNDI
//check bindings at comp
Context sub1 = (Context)initCtx.lookup("java:comp");
assertNotNull(sub1);
Context sub2 = sub1.createSubcontext ("env");
assertNotNull(sub2);
initCtx.bind ("java:comp/env/rubbish", "abc");
assertEquals ("abc", initCtx.lookup("java:comp/env/rubbish"));
@ -302,7 +440,6 @@ public class TestJNDI
}
//test what happens when you close an initial context that was used
initCtx.close();
}
finally
@ -317,61 +454,7 @@ public class TestJNDI
comp.destroySubcontext("env");
comp.unbind("crud");
comp.unbind("crud2");
}
}
@Test
public void testParent()
throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader parentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], parentLoader);
try
{
//Test creating a comp for the parent loader does not leak to child
InitialContext initCtx = new InitialContext();
Context comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
Context env = (Context)comp.createSubcontext("env");
assertNotNull(env);
env.bind("foo", "aaabbbcccddd");
assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo"));
//Change to child loader
currentThread.setContextClassLoader(childLoader1);
comp = (Context)initCtx.lookup("java:comp");
Context childEnv = (Context)comp.createSubcontext("env");
assertNotSame(env, childEnv);
childEnv.bind("foo", "eeefffggghhh");
assertEquals("eeefffggghhh", (String)initCtx.lookup("java:comp/env/foo"));
//Change back to parent
currentThread.setContextClassLoader(parentLoader);
assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo"));
}
finally
{
//make some effort to clean up
InitialContext ic = new InitialContext();
currentThread.setContextClassLoader(parentLoader);
Context comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env");
currentThread.setContextClassLoader(childLoader1);
comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env");
currentThread.setContextClassLoader(currentLoader);
}
}
}

View File

@ -224,6 +224,15 @@ public class TestLocalJNDI
assertEquals("333", (String)o);
assertEquals("333", ic.lookup(name));
ic.destroySubcontext("a");
try
{
ic.lookup("a");
fail("context a was not destroyed");
}
catch (NameNotFoundException e)
{
//expected
}
name = parser.parse("");
name.add("x");

View File

@ -25,7 +25,7 @@ import java.util.List;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import edu.emory.mathcs.backport.java.util.Arrays;
import java.util.Arrays;
/**
* OverlayConfig

View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- =============================================================== -->
<!-- Mixin the Low Resources Monitor -->
<!-- =============================================================== -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<Call name="addBean">
<Arg>
<New class="org.eclipse.jetty.server.LowResourceMonitor">
<Arg name="server"><Ref refid='Server'/></Arg>
<Set name="period">1000</Set>
<Set name="lowResourcesIdleTimeout">200</Set>
<Set name="monitorThreads">true</Set>
<Set name="maxConnections">0</Set>
<Set name="maxMemory">0</Set>
<Set name="maxLowResourcesTime">5000</Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -23,10 +23,13 @@ import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@ -35,6 +38,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -141,16 +145,19 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
private final Scheduler _scheduler;
private final ByteBufferPool _byteBufferPool;
private final Thread[] _acceptors;
private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap());
private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
private volatile CountDownLatch _stopping;
private long _idleTimeout = 30000;
private String _defaultProtocol;
private ConnectionFactory _defaultConnectionFactory;
/**
* @param server The server this connector will be added to. Must not be null.
* @param executor An executor for this connector or null to use the servers executor
* @param scheduler A scheduler for this connector or null to a new {@link TimerScheduler} instance.
* @param pool A buffer pool for this connector or null to use a default {@link ByteBufferPool}
* @param scheduler A scheduler for this connector or null to either a {@link Scheduler} set as a server bean or if none set, then a new {@link TimerScheduler} instance.
* @param pool A buffer pool for this connector or null to either a {@link ByteBufferPool} set as a server bean or none set, the new {@link ArrayByteBufferPool} instance.
* @param acceptors the number of acceptor threads to use, or 0 for a default value.
* @param factories The Connection Factories to use.
*/
@ -164,7 +171,11 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
_server=server;
_executor=executor!=null?executor:_server.getThreadPool();
if (scheduler==null)
scheduler=_server.getBean(Scheduler.class);
_scheduler=scheduler!=null?scheduler:new TimerScheduler();
if (pool==null)
pool=_server.getBean(ByteBufferPool.class);
_byteBufferPool = pool!=null?pool:new ArrayByteBufferPool();
addBean(_server,false);
@ -468,6 +479,9 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
}
}
// protected void connectionOpened(Connection connection)
// {
// _stats.connectionOpened();
@ -488,6 +502,22 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
// newConnection.onOpen();
// }
@Override
public Collection<EndPoint> getConnectedEndPoints()
{
return _immutableEndPoints;
}
protected void onEndPointOpened(EndPoint endp)
{
_endpoints.add(endp);
}
protected void onEndPointClosed(EndPoint endp)
{
_endpoints.remove(endp);
}
@Override
public Scheduler getScheduler()
{

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Graceful;
@ -85,5 +86,8 @@ public interface Connector extends LifeCycle, Graceful
*/
public Object getTransport();
/**
* @return immutable collection of connected endpoints
*/
public Collection<EndPoint> getConnectedEndPoints();
}

View File

@ -167,6 +167,7 @@ public class LocalConnector extends AbstractConnector
LOG.debug("accepting {}", acceptorID);
LocalEndPoint endPoint = _connects.take();
endPoint.onOpen();
onEndPointOpened(endPoint);
Connection connection = getDefaultConnectionFactory().newConnection(this, endPoint);
endPoint.setConnection(connection);
@ -209,6 +210,7 @@ public class LocalConnector extends AbstractConnector
@Override
public void onClose()
{
LocalConnector.this.onEndPointClosed(this);
super.onClose();
_closed.countDown();
}

View File

@ -0,0 +1,349 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
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.ThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
/* ------------------------------------------------------------ */
/** A monitor for low resources
* <p>An instance of this class will monitor all the connectors of a server (or a set of connectors
* configured with {@link #setMonitoredConnectors(Collection)}) for a low resources state.
* Low resources can be detected by:<ul>
* <li>{@link ThreadPool#isLowOnThreads()} if {@link Connector#getExecutor()} is
* an instance of {@link ThreadPool} and {@link #setMonitorThreads(boolean)} is true.<li>
* <li>If {@link #setMaxMemory(long)} is non zero then low resources is detected if the JVMs
* {@link Runtime} instance has {@link Runtime#totalMemory()} minus {@link Runtime#freeMemory()}
* greater than {@link #getMaxMemory()}</li>
* <li>If {@link #setMaxConnections(int)} is non zero then low resources is dected if the total number
* of connections exceeds {@link #getMaxConnections()}</li>
* </ul>
* </p>
* <p>Once low resources state is detected, the cause is logged and all existing connections returned
* by {@link Connector#getConnectedEndPoints()} have {@link EndPoint#setIdleTimeout(long)} set
* to {@link #getLowResourcesIdleTimeout()}. New connections are not affected, however if the low
* resources state persists for more than {@link #getMaxLowResourcesTime()}, then the
* {@link #getLowResourcesIdleTimeout()} to all connections again. Once the low resources state is
* cleared, the idle timeout is reset to the connector default given by {@link Connector#getIdleTimeout()}.
* </p>
*/
@ManagedObject ("Monitor for low resource conditions and activate a low resource mode if detected")
public class LowResourceMonitor extends AbstractLifeCycle
{
private static final Logger LOG = Log.getLogger(LowResourceMonitor.class);
private final Server _server;
private Scheduler _scheduler;
private Connector[] _monitoredConnectors;
private int _period=1000;
private int _maxConnections;
private long _maxMemory;
private int _lowResourcesIdleTimeout=1000;
private int _maxLowResourcesTime=0;
private boolean _monitorThreads=true;
private final AtomicBoolean _low = new AtomicBoolean();
private String _cause;
private String _reasons;
private long _lowStarted;
private final Runnable _monitor = new Runnable()
{
@Override
public void run()
{
if (isRunning())
{
monitor();
_scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
}
}
};
public LowResourceMonitor(@Name("server") Server server)
{
_server=server;
}
@ManagedAttribute("Are the monitored connectors low on resources?")
public boolean isLowOnResources()
{
return _low.get();
}
@ManagedAttribute("The reason(s) the monitored connectors are low on resources")
public String getLowResourcesReasons()
{
return _reasons;
}
@ManagedAttribute("Get the timestamp in ms since epoch that low resources state started")
public long getLowResourcesStarted()
{
return _lowStarted;
}
@ManagedAttribute("The monitored connectors. If null then all server connectors are monitored")
public Collection<Connector> getMonitoredConnectors()
{
if (_monitoredConnectors==null)
return Collections.emptyList();
return Arrays.asList(_monitoredConnectors);
}
/**
* @param monitoredConnectors The collections of Connectors that should be monitored for low resources.
*/
public void setMonitoredConnectors(Collection<Connector> monitoredConnectors)
{
if (monitoredConnectors==null || monitoredConnectors.size()==0)
_monitoredConnectors=null;
else
_monitoredConnectors = monitoredConnectors.toArray(new Connector[monitoredConnectors.size()]);
}
@ManagedAttribute("The monitor period in ms")
public int getPeriod()
{
return _period;
}
/**
* @param periodMS The period in ms to monitor for low resources
*/
public void setPeriod(int periodMS)
{
_period = periodMS;
}
@ManagedAttribute("True if low available threads status is monitored")
public boolean getMonitorThreads()
{
return _monitorThreads;
}
/**
* @param monitorThreads If true, check connectors executors to see if they are
* {@link ThreadPool} instances that are low on threads.
*/
public void setMonitorThreads(boolean monitorThreads)
{
_monitorThreads = monitorThreads;
}
@ManagedAttribute("The maximum connections allowed for the monitored connectors before low resource handling is activated")
public int getMaxConnections()
{
return _maxConnections;
}
/**
* @param maxConnections The maximum connections before low resources state is triggered
*/
public void setMaxConnections(int maxConnections)
{
_maxConnections = maxConnections;
}
@ManagedAttribute("The maximum memory (in bytes) that can be used before low resources is triggered. Memory used is calculated as (totalMemory-freeMemory).")
public long getMaxMemory()
{
return _maxMemory;
}
/**
* @param maxMemoryBytes The maximum memory in bytes in use before low resources is triggered.
*/
public void setMaxMemory(long maxMemoryBytes)
{
_maxMemory = maxMemoryBytes;
}
@ManagedAttribute("The idletimeout in ms to apply to all existing connections when low resources is detected")
public int getLowResourcesIdleTimeout()
{
return _lowResourcesIdleTimeout;
}
/**
* @param lowResourcesIdleTimeoutMS The timeout in ms to apply to EndPoints when in the low resources state.
*/
public void setLowResourcesIdleTimeout(int lowResourcesIdleTimeoutMS)
{
_lowResourcesIdleTimeout = lowResourcesIdleTimeoutMS;
}
@ManagedAttribute("The maximum time in ms that low resources condition can persist before lowResourcesIdleTimeout is applied to new connections as well as existing connections")
public int getMaxLowResourcesTime()
{
return _maxLowResourcesTime;
}
/**
* @param maxLowResourcesTimeMS The time in milliseconds that a low resource state can persist before the low resource idle timeout is reapplied to all connections
*/
public void setMaxLowResourcesTime(int maxLowResourcesTimeMS)
{
_maxLowResourcesTime = maxLowResourcesTimeMS;
}
@Override
protected void doStart() throws Exception
{
_scheduler = _server.getBean(Scheduler.class);
if (_scheduler==null)
{
_scheduler=new LRMScheduler();
_scheduler.start();
}
super.doStart();
_scheduler.schedule(_monitor,_period,TimeUnit.MILLISECONDS);
}
@Override
protected void doStop() throws Exception
{
if (_scheduler instanceof LRMScheduler)
_scheduler.stop();
super.doStop();
}
protected Connector[] getMonitoredOrServerConnectors()
{
if (_monitoredConnectors!=null && _monitoredConnectors.length>0)
return _monitoredConnectors;
return _server.getConnectors();
}
protected void monitor()
{
String reasons=null;
String cause="";
int connections=0;
for(Connector connector : getMonitoredOrServerConnectors())
{
connections+=connector.getConnectedEndPoints().size();
Executor executor = connector.getExecutor();
if (executor instanceof ThreadPool)
{
ThreadPool threadpool=(ThreadPool) executor;
if (_monitorThreads && threadpool.isLowOnThreads())
{
reasons=low(reasons,"Low on threads: "+threadpool);
cause+="T";
}
}
}
if (_maxConnections>0 && connections>_maxConnections)
{
reasons=low(reasons,"Max Connections exceeded: "+connections+">"+_maxConnections);
cause+="C";
}
long memory=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
if (_maxMemory>0 && memory>_maxMemory)
{
reasons=low(reasons,"Max memory exceeded: "+memory+">"+_maxMemory);
cause+="M";
}
if (reasons!=null)
{
// Log the reasons if there is any change in the cause
if (!cause.equals(_cause))
{
LOG.warn("Low Resources: {}",reasons);
_cause=cause;
}
// Enter low resources state?
if (_low.compareAndSet(false,true))
{
_reasons=reasons;
_lowStarted=System.currentTimeMillis();
setLowResources();
}
// Too long in low resources state?
if (_maxLowResourcesTime>0 && (System.currentTimeMillis()-_lowStarted)>_maxLowResourcesTime)
setLowResources();
}
else
{
if (_low.compareAndSet(true,false))
{
LOG.info("Low Resources cleared");
_reasons=null;
_lowStarted=0;
clearLowResources();
}
}
}
protected void setLowResources()
{
for(Connector connector : getMonitoredOrServerConnectors())
{
for (EndPoint endPoint : connector.getConnectedEndPoints())
endPoint.setIdleTimeout(_lowResourcesIdleTimeout);
}
}
protected void clearLowResources()
{
for(Connector connector : getMonitoredOrServerConnectors())
{
for (EndPoint endPoint : connector.getConnectedEndPoints())
endPoint.setIdleTimeout(connector.getIdleTimeout());
}
}
private String low(String reasons, String newReason)
{
if (reasons==null)
return newReason;
return reasons+", "+newReason;
}
private static class LRMScheduler extends TimerScheduler
{
}
}

View File

@ -400,5 +400,21 @@ public class ServerConnector extends AbstractNetworkConnector
{
return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
}
@Override
protected void endPointOpened(EndPoint endpoint)
{
super.endPointOpened(endpoint);
onEndPointOpened(endpoint);
}
@Override
protected void endPointClosed(EndPoint endpoint)
{
onEndPointClosed(endpoint);
super.endPointClosed(endpoint);
}
}
}

View File

@ -0,0 +1,197 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class LowResourcesMonitorTest
{
QueuedThreadPool _threadPool;
Server _server;
ServerConnector _connector;
LowResourceMonitor _lowResourcesMonitor;
@Before
public void before() throws Exception
{
_threadPool = new QueuedThreadPool();
_threadPool.setMaxThreads(50);
_server = new Server(_threadPool);
_server.manage(_threadPool);
_server.addBean(new TimerScheduler());
_connector = new ServerConnector(_server);
_connector.setPort(0);
_connector.setIdleTimeout(35000);
_server.addConnector(_connector);
_server.setHandler(new DumpHandler());
_lowResourcesMonitor=new LowResourceMonitor(_server);
_lowResourcesMonitor.setLowResourcesIdleTimeout(200);
_lowResourcesMonitor.setMaxConnections(20);
_lowResourcesMonitor.setPeriod(900);
_server.addBean(_lowResourcesMonitor);
_server.start();
}
@After
public void after() throws Exception
{
_server.stop();
}
@Test
public void testLowOnThreads() throws Exception
{
_threadPool.setMaxThreads(_threadPool.getThreads()-_threadPool.getIdleThreads()+10);
Thread.sleep(1200);
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
final CountDownLatch latch = new CountDownLatch(1);
for (int i=0;i<20;i++)
{
_threadPool.dispatch(new Runnable()
{
@Override
public void run()
{
try
{
latch.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
});
}
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
latch.countDown();
Thread.sleep(1200);
System.err.println(_threadPool.dump());
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
}
@Ignore ("not reliable")
@Test
public void testLowOnMemory() throws Exception
{
_lowResourcesMonitor.setMaxMemory(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()+(100*1024*1024));
Thread.sleep(1200);
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
byte[] data = new byte[100*1024*1024];
Arrays.fill(data,(byte)1);
int hash = Arrays.hashCode(data);
assertThat(hash,not(equalTo(0)));
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
data=null;
System.gc();
System.gc();
Thread.sleep(1200);
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
}
@Test
public void testMaxConnectionsAndMaxIdleTime() throws Exception
{
_lowResourcesMonitor.setMaxMemory(0);
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
Socket[] socket = new Socket[_lowResourcesMonitor.getMaxConnections()+1];
for (int i=0;i<socket.length;i++)
socket[i]=new Socket("localhost",_connector.getLocalPort());
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
Socket newSocket = new Socket("localhost",_connector.getLocalPort());
// wait for low idle time to close sockets, but not new Socket
Thread.sleep(1200);
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
for (int i=0;i<socket.length;i++)
Assert.assertEquals(-1,socket[i].getInputStream().read());
newSocket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StringUtil.__UTF8_CHARSET));
Assert.assertEquals('H',newSocket.getInputStream().read());
}
@Test
public void testMaxLowResourceTime() throws Exception
{
_lowResourcesMonitor.setMaxLowResourcesTime(2000);
Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
Socket socket0 = new Socket("localhost",_connector.getLocalPort());
_lowResourcesMonitor.setMaxMemory(1);
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
Socket socket1 = new Socket("localhost",_connector.getLocalPort());
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
Assert.assertEquals(-1,socket0.getInputStream().read());
socket1.getOutputStream().write("G".getBytes(StringUtil.__UTF8_CHARSET));
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
socket1.getOutputStream().write("E".getBytes(StringUtil.__UTF8_CHARSET));
Thread.sleep(1200);
Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
Assert.assertEquals(-1,socket1.getInputStream().read());
}
}

View File

@ -90,6 +90,8 @@ import org.eclipse.jetty.util.log.Logger;
* deflateNoWrap The noWrap setting for deflate compression. Defaults to true. (true/false)
* See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
*
* methods Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
*
* mimeTypes Comma separated list of mime types to compress. See description above.
*
* excludedAgents Comma separated list of user agents to exclude from compression. Does a
@ -127,6 +129,8 @@ public class GzipFilter extends UserAgentFilter
protected int _minGzipSize=256;
protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
protected boolean _deflateNoWrap = true;
protected final Set<String> _methods=new HashSet<String>();
protected Set<String> _excludedAgents;
protected Set<Pattern> _excludedAgentPatterns;
protected Set<String> _excludedPaths;
@ -166,6 +170,16 @@ public class GzipFilter extends UserAgentFilter
if (tmp!=null)
_deflateNoWrap=Boolean.parseBoolean(tmp);
tmp=filterConfig.getInitParameter("methods");
if (tmp!=null)
{
StringTokenizer tok = new StringTokenizer(tmp,",",false);
while (tok.hasMoreTokens())
_methods.add(tok.nextToken().trim().toUpperCase());
}
else
_methods.add(HttpMethod.GET.asString());
tmp=filterConfig.getInitParameter("mimeTypes");
if (tmp!=null)
{
@ -235,9 +249,9 @@ public class GzipFilter extends UserAgentFilter
HttpServletRequest request=(HttpServletRequest)req;
HttpServletResponse response=(HttpServletResponse)res;
// If not a GET or an Excluded URI - no Vary because no matter what client, this URI is always excluded
// If not a supported method or it is an Excluded URI - no Vary because no matter what client, this URI is always excluded
String requestURI = request.getRequestURI();
if (!HttpMethod.GET.is(request.getMethod()) || isExcludedPath(requestURI))
if (!_methods.contains(request.getMethod()) || isExcludedPath(requestURI))
{
super.doFilter(request,response,chain);
return;

View File

@ -112,7 +112,7 @@ public class GzipFilterContentLengthTest
try
{
tester.start();
tester.assertIsResponseGzipCompressed(testfile.getName());
tester.assertIsResponseGzipCompressed("GET",testfile.getName());
}
finally
{
@ -132,7 +132,7 @@ public class GzipFilterContentLengthTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed(testfile.getName(),filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET",testfile.getName(),filesize,HttpStatus.OK_200);
}
finally
{

View File

@ -103,6 +103,50 @@ public class GzipFilterDefaultTest
@Rule
public TestingDir testingdir = new TestingDir();
@Test
public void testIsGzipByMethod() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType);
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE * 2;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(GetServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
holder.setInitParameter("methods","POST,WIBBLE");
try
{
tester.start();
tester.assertIsResponseGzipCompressed("POST","file.txt");
tester.assertIsResponseGzipCompressed("WIBBLE","file.txt");
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,200);
}
finally
{
tester.stop();
}
}
public static class GetServlet extends DefaultServlet
{
public GetServlet()
{
super();
}
@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws IOException,ServletException
{
doGet(req,resp);
}
}
@Test
public void testIsGzipCompressedTiny() throws Exception
{
@ -118,7 +162,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
@ -142,7 +186,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
@ -166,7 +210,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
@ -190,7 +234,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseGzipCompressed("file.txt");
HttpTester.Response http = tester.assertIsResponseGzipCompressed("GET","file.txt");
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
@ -213,7 +257,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseNotGzipCompressed("file.txt", filesize, HttpStatus.OK_200);
HttpTester.Response http = tester.assertIsResponseNotGzipCompressed("GET","file.txt", filesize, HttpStatus.OK_200);
Assert.assertEquals("Accept-Encoding",http.get("Vary"));
}
finally
@ -236,7 +280,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
HttpTester.Response http = tester.assertIsResponseNotGzipCompressed("file.mp3", filesize, HttpStatus.OK_200);
HttpTester.Response http = tester.assertIsResponseNotGzipCompressed("GET","file.mp3", filesize, HttpStatus.OK_200);
Assert.assertNull(http.get("Vary"));
}
finally
@ -257,7 +301,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed(-1, 204);
tester.assertIsResponseNotGzipCompressed("GET",-1, 204);
}
finally
{
@ -278,7 +322,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressedAndEqualToExpectedString("error message", -1, 400);
tester.assertIsResponseNotGzipCompressedAndEqualToExpectedString("GET","error message", -1, 400);
}
finally
{
@ -302,7 +346,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{
@ -326,7 +370,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{
@ -348,7 +392,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{
@ -370,7 +414,7 @@ public class GzipFilterDefaultTest
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt",filesize,HttpStatus.OK_200);
tester.assertIsResponseNotGzipCompressed("GET","file.txt",filesize,HttpStatus.OK_200);
}
finally
{

View File

@ -107,7 +107,7 @@ public class IncludableGzipFilterMinSizeTest
try {
tester.start();
tester.assertIsResponseGzipCompressed("big_script.js");
tester.assertIsResponseGzipCompressed("GET","big_script.js");
} finally {
tester.stop();
}

View File

@ -74,18 +74,18 @@ public class GzipTester
// DOES NOT WORK IN WINDOWS - this.testdir.ensureEmpty();
}
public HttpTester.Response assertIsResponseGzipCompressed(String filename) throws Exception
public HttpTester.Response assertIsResponseGzipCompressed(String method, String filename) throws Exception
{
return assertIsResponseGzipCompressed(filename,filename);
return assertIsResponseGzipCompressed(method,filename,filename);
}
public HttpTester.Response assertIsResponseGzipCompressed(String requestedFilename, String serverFilename) throws Exception
public HttpTester.Response assertIsResponseGzipCompressed(String method, String requestedFilename, String serverFilename) throws Exception
{
// System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
request.setMethod("GET");
request.setMethod(method);
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);
@ -238,10 +238,10 @@ public class GzipTester
* passing -1 will disable the Content-Length assertion)
* @throws Exception
*/
public HttpTester.Response assertIsResponseNotGzipCompressed(String filename, int expectedFilesize, int status) throws Exception
public HttpTester.Response assertIsResponseNotGzipCompressed(String method, String filename, int expectedFilesize, int status) throws Exception
{
String uri = "/context/"+filename;
HttpTester.Response response = executeRequest(uri);
HttpTester.Response response = executeRequest(method,uri);
assertResponseHeaders(expectedFilesize,status,response);
// Assert that the contents are what we expect.
@ -269,10 +269,10 @@ public class GzipTester
* passing -1 will disable the Content-Length assertion)
* @throws Exception
*/
public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String expectedResponse, int expectedFilesize, int status) throws Exception
public void assertIsResponseNotGzipCompressedAndEqualToExpectedString(String method,String expectedResponse, int expectedFilesize, int status) throws Exception
{
String uri = "/context/";
HttpTester.Response response = executeRequest(uri);
HttpTester.Response response = executeRequest(method,uri);
assertResponseHeaders(expectedFilesize,status,response);
String actual = readResponse(response);
@ -288,10 +288,10 @@ public class GzipTester
* passing -1 will disable the Content-Length assertion)
* @throws Exception
*/
public void assertIsResponseNotGzipCompressed(int expectedFilesize, int status) throws Exception
public void assertIsResponseNotGzipCompressed(String method,int expectedFilesize, int status) throws Exception
{
String uri = "/context/";
HttpTester.Response response = executeRequest(uri);
HttpTester.Response response = executeRequest(method,uri);
assertResponseHeaders(expectedFilesize,status,response);
}
@ -312,13 +312,13 @@ public class GzipTester
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),not(containsString(compressionType)));
}
private HttpTester.Response executeRequest(String uri) throws IOException, Exception
private HttpTester.Response executeRequest(String method, String uri) throws IOException, Exception
{
//System.err.printf("[GzipTester] requesting %s%n",uri);
HttpTester.Request request = HttpTester.newRequest();
HttpTester.Response response;
request.setMethod("GET");
request.setMethod(method);
request.setVersion("HTTP/1.0");
request.setHeader("Host","tester");
request.setHeader("Accept-Encoding",compressionType);

View File

@ -59,6 +59,9 @@ public interface IStream extends Stream, Callback
*/
public void setStreamFrameListener(StreamFrameListener listener);
//TODO: javadoc thomas
public StreamFrameListener getStreamFrameListener();
/**
* <p>A stream can be open, {@link #isHalfClosed() half closed} or
* {@link #isClosed() closed} and this method updates the close state

View File

@ -45,9 +45,10 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.PingInfo;
import org.eclipse.jetty.spdy.api.PingResultInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.SPDYException;
import org.eclipse.jetty.spdy.api.Session;
@ -498,8 +499,17 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
stream.process(frame);
// Update the last stream id before calling the application (which may send a GO_AWAY)
updateLastStreamId(stream);
SynInfo synInfo = new SynInfo(frame.getHeaders(), frame.isClose(), frame.getPriority());
StreamFrameListener streamListener = notifyOnSyn(listener, stream, synInfo);
StreamFrameListener streamListener;
if (stream.isUnidirectional())
{
PushInfo pushInfo = new PushInfo(frame.getHeaders(), frame.isClose());
streamListener = notifyOnPush(stream.getAssociatedStream().getStreamFrameListener(), stream, pushInfo);
}
else
{
SynInfo synInfo = new SynInfo(frame.getHeaders(), frame.isClose(), frame.getPriority());
streamListener = notifyOnSyn(listener, stream, synInfo);
}
stream.setStreamFrameListener(streamListener);
flush();
// The onSyn() listener may have sent a frame that closed the stream
@ -680,9 +690,9 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
{
if (goAwayReceived.compareAndSet(false, true))
{
//TODO: Find a better name for GoAwayReceivedInfo
GoAwayReceivedInfo goAwayReceivedInfo = new GoAwayReceivedInfo(frame.getLastStreamId(), SessionStatus.from(frame.getStatusCode()));
notifyOnGoAway(listener, goAwayReceivedInfo);
//TODO: Find a better name for GoAwayResultInfo
GoAwayResultInfo goAwayResultInfo = new GoAwayResultInfo(frame.getLastStreamId(), SessionStatus.from(frame.getStatusCode()));
notifyOnGoAway(listener, goAwayResultInfo);
flush();
// SPDY does not require to send back a response to a GO_AWAY.
// We notified the application of the last good stream id and
@ -755,6 +765,27 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
}
}
private StreamFrameListener notifyOnPush(StreamFrameListener listener, Stream stream, PushInfo pushInfo)
{
try
{
if (listener == null)
return null;
LOG.debug("Invoking callback with {} on listener {}", pushInfo, listener);
return listener.onPush(stream, pushInfo);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
return null;
}
catch (Error x)
{
LOG.info("Exception while notifying listener " + listener, x);
throw x;
}
}
private StreamFrameListener notifyOnSyn(SessionFrameListener listener, Stream stream, SynInfo synInfo)
{
try
@ -839,14 +870,14 @@ public class StandardSession implements ISession, Parser.Listener, Dumpable
}
}
private void notifyOnGoAway(SessionFrameListener listener, GoAwayReceivedInfo goAwayReceivedInfo)
private void notifyOnGoAway(SessionFrameListener listener, GoAwayResultInfo goAwayResultInfo)
{
try
{
if (listener != null)
{
LOG.debug("Invoking callback with {} on listener {}", goAwayReceivedInfo, listener);
listener.onGoAway(this, goAwayReceivedInfo);
LOG.debug("Invoking callback with {} on listener {}", goAwayResultInfo, listener);
listener.onGoAway(this, goAwayResultInfo);
}
}
catch (Exception x)

View File

@ -148,6 +148,7 @@ public class StandardStream implements IStream
this.listener = listener;
}
@Override
public StreamFrameListener getStreamFrameListener()
{
return listener;

View File

@ -22,18 +22,18 @@ package org.eclipse.jetty.spdy.api;
* <p>A container for GOAWAY frames metadata: the last good stream id and
* the session status.</p>
*/
public class GoAwayReceivedInfo
public class GoAwayResultInfo
{
private final int lastStreamId;
private final SessionStatus sessionStatus;
/**
* <p>Creates a new {@link GoAwayReceivedInfo} with the given last good stream id and session status</p>
* <p>Creates a new {@link GoAwayResultInfo} with the given last good stream id and session status</p>
*
* @param lastStreamId the last good stream id
* @param sessionStatus the session status
*/
public GoAwayReceivedInfo(int lastStreamId, SessionStatus sessionStatus)
public GoAwayResultInfo(int lastStreamId, SessionStatus sessionStatus)
{
this.lastStreamId = lastStreamId;
this.sessionStatus = sessionStatus;

View File

@ -36,7 +36,7 @@ public interface SessionFrameListener extends EventListener
* <p>Application code should implement this method and reply to the stream creation, eventually
* sending data:</p>
* <pre>
* public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
* public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
* {
* // Do something with the metadata contained in synInfo
*
@ -52,7 +52,7 @@ public interface SessionFrameListener extends EventListener
* </pre>
* <p>Alternatively, if the stream creation requires reading data sent from the other peer:</p>
* <pre>
* public Stream.FrameListener onSyn(Stream stream, SynInfo synInfo)
* public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
* {
* // Do something with the metadata contained in synInfo
*
@ -106,9 +106,9 @@ public interface SessionFrameListener extends EventListener
* <p>Callback invoked when the other peer signals that it is closing the connection.</p>
*
* @param session the session
* @param goAwayReceivedInfo the metadata sent
* @param goAwayResultInfo the metadata sent
*/
public void onGoAway(Session session, GoAwayReceivedInfo goAwayReceivedInfo);
public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo);
/**
* <p>Callback invoked when an exception is thrown during the processing of an event on a
@ -119,6 +119,7 @@ public interface SessionFrameListener extends EventListener
*/
public void onException(Throwable x);
/**
* <p>Empty implementation of {@link SessionFrameListener}</p>
*/
@ -148,7 +149,7 @@ public interface SessionFrameListener extends EventListener
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayReceivedInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
{
}

View File

@ -50,6 +50,15 @@ public interface StreamFrameListener extends EventListener
*/
public void onHeaders(Stream stream, HeadersInfo headersInfo);
/**
* <p>Callback invoked when a push syn has been received on a stream.</p>
*
* @param stream the push stream just created
* @param pushInfo
* @return a listener for stream events or null if there is no interest in being notified of stream events
*/
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo);
/**
* <p>Callback invoked when data bytes are received on a stream.</p>
* <p>Implementers should be read or consume the content of the
@ -75,6 +84,12 @@ public interface StreamFrameListener extends EventListener
{
}
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
return null;
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{

View File

@ -60,14 +60,24 @@ public class ClientUsageTest
}
@Test
public void testClientRequestWithBodyResponseNoBody() throws Exception
public void testClientReceivesPush1() throws InterruptedException, ExecutionException, TimeoutException
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0),
new StreamFrameListener.Adapter()
session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
return new Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
}
};
};
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
@ -83,6 +93,71 @@ public class ClientUsageTest
throw new IllegalStateException(e);
}
}
});
}
@Test
public void testClientReceivesPush2() throws InterruptedException, ExecutionException, TimeoutException
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, new SessionFrameListener.Adapter()
{
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
return new StreamFrameListener.Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
}
};
}
}, null, null);
session.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
replyInfo.getHeaders().get("host");
// Then issue another similar request
try
{
stream.getSession().syn(new SynInfo(new Fields(), true), this);
}
catch (ExecutionException | InterruptedException | TimeoutException e)
{
throw new IllegalStateException(e);
}
}
});
}
@Test
public void testClientRequestWithBodyResponseNoBody() throws Exception
{
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
Stream stream = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), false, (byte)0),
new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
replyInfo.getHeaders().get("host");
// Then issue another similar request
try
{
stream.getSession().syn(new SynInfo(new Fields(), true), this);
}
catch (ExecutionException | InterruptedException | TimeoutException e)
{
throw new IllegalStateException(e);
}
}
});
// Send-and-forget the data
stream.data(new StringDataInfo("data", true));
@ -96,38 +171,39 @@ public class ClientUsageTest
final String context = "context";
session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
replyInfo.getHeaders().get("host");
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
replyInfo.getHeaders().get("host");
// Then issue another similar request
try
{
stream.getSession().syn(new SynInfo(new Fields(), true), this);
}
catch (ExecutionException | InterruptedException | TimeoutException e)
{
throw new IllegalStateException(e);
}
}
// Then issue another similar request
try
{
stream.getSession().syn(new SynInfo(new Fields(), true), this);
}
catch (ExecutionException | InterruptedException | TimeoutException e)
{
throw new IllegalStateException(e);
}
}
}, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
// Differently from JDK 7 AIO, there is no need to
// have an explicit parameter for the context since
// that is captured while the handler is created anyway,
// and it is used only by the handler as parameter
public void succeeded(Stream stream)
{
// Differently from JDK 7 AIO, there is no need to
// have an explicit parameter for the context since
// that is captured while the handler is created anyway,
// and it is used only by the handler as parameter
// The style below is fire-and-forget, since
// we do not pass the handler nor we call get()
// to wait for the data to be sent
stream.data(new StringDataInfo(context, true), new Callback.Adapter());
}
});
// The style below is fire-and-forget, since
// we do not pass the handler nor we call get()
// to wait for the data to be sent
stream.data(new StringDataInfo(context, true), new Callback.Adapter());
}
}
);
}
@Test
@ -136,48 +212,49 @@ public class ClientUsageTest
Session session = new StandardSession(SPDY.V2, null, null, null, null, null, null, 1, null, null, null);
session.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
{
// The good of passing the listener to push() is that applications can safely
// accumulate info from the reply headers to be used in the data callback,
// e.g. content-type, charset, etc.
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
Fields headers = replyInfo.getHeaders();
int contentLength = headers.get("content-length").valueAsInt();
stream.setAttribute("content-length", contentLength);
if (!replyInfo.isClose())
stream.setAttribute("builder", new StringBuilder());
// May issue another similar request while waiting for data
try
{
stream.getSession().syn(new SynInfo(new Fields(), true), this);
}
catch (ExecutionException | InterruptedException | TimeoutException e)
// The good of passing the listener to push() is that applications can safely
// accumulate info from the reply headers to be used in the data callback,
// e.g. content-type, charset, etc.
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Do something with the response
Fields headers = replyInfo.getHeaders();
int contentLength = headers.get("content-length").valueAsInt();
stream.setAttribute("content-length", contentLength);
if (!replyInfo.isClose())
stream.setAttribute("builder", new StringBuilder());
// May issue another similar request while waiting for data
try
{
stream.getSession().syn(new SynInfo(new Fields(), true), this);
}
catch (ExecutionException | InterruptedException | TimeoutException e)
{
throw new IllegalStateException(e);
}
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
StringBuilder builder = (StringBuilder)stream.getAttribute("builder");
builder.append(dataInfo.asString("UTF-8", true));
}
}, new Promise.Adapter<Stream>()
{
throw new IllegalStateException(e);
@Override
public void succeeded(Stream stream)
{
stream.data(new BytesDataInfo("wee".getBytes(Charset.forName("UTF-8")), false), new Callback.Adapter());
stream.data(new StringDataInfo("foo", false), new Callback.Adapter());
stream.data(new ByteBufferDataInfo(Charset.forName("UTF-8").encode("bar"), true), new Callback.Adapter());
}
}
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
StringBuilder builder = (StringBuilder)stream.getAttribute("builder");
builder.append(dataInfo.asString("UTF-8", true));
}
}, new Promise.Adapter<Stream>()
{
@Override
public void succeeded(Stream stream)
{
stream.data(new BytesDataInfo("wee".getBytes(Charset.forName("UTF-8")), false), new Callback.Adapter());
stream.data(new StringDataInfo("foo", false), new Callback.Adapter());
stream.data(new ByteBufferDataInfo(Charset.forName("UTF-8").encode("bar"), true), new Callback.Adapter());
}
});
);
}
}

View File

@ -24,6 +24,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.HeadersInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
@ -131,6 +132,12 @@ public class HTTPSPDYServerConnectionFactory extends SPDYServerConnectionFactory
channel.requestHeaders(headersInfo.getHeaders(), headersInfo.isClose());
}
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
return null;
}
@Override
public void onData(Stream stream, final DataInfo dataInfo)
{

View File

@ -23,7 +23,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.PingResultInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.Session;
@ -104,7 +104,7 @@ public class ProxyEngineSelector extends ServerSessionFrameListener.Adapter
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayReceivedInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
{
// TODO:
}

View File

@ -42,7 +42,7 @@ import org.eclipse.jetty.spdy.StandardStream;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.HeadersInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
@ -136,7 +136,7 @@ public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParse
{
assert content == null;
if (headers.isEmpty())
proxyEngineSelector.onGoAway(session, new GoAwayReceivedInfo(0, SessionStatus.OK));
proxyEngineSelector.onGoAway(session, new GoAwayResultInfo(0, SessionStatus.OK));
else
syn(true);
}

View File

@ -29,8 +29,9 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.HeadersInfo;
import org.eclipse.jetty.spdy.api.Info;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
@ -50,8 +51,8 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* <p>{@link SPDYProxyEngine} implements a SPDY to SPDY proxy, that is, converts SPDY events received by
* clients into SPDY events for the servers.</p>
* <p>{@link SPDYProxyEngine} implements a SPDY to SPDY proxy, that is, converts SPDY events received by clients into
* SPDY events for the servers.</p>
*/
public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
{
@ -131,6 +132,12 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
}
}
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
throw new IllegalStateException("We shouldn't receive pushes from clients");
}
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -222,6 +229,61 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
this.clientStream = clientStream;
}
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
LOG.debug("S -> P pushed {} on {}", pushInfo, stream);
Fields headers = new Fields(pushInfo.getHeaders(), false);
addResponseProxyHeaders(stream, headers);
customizeResponseHeaders(stream, headers);
Stream clientStream = (Stream)stream.getAssociatedStream().getAttribute
(CLIENT_STREAM_ATTRIBUTE);
convert(stream.getSession().getVersion(), clientStream.getSession().getVersion(),
headers);
StreamHandler handler = new StreamHandler(clientStream, pushInfo);
stream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
clientStream.push(new PushInfo(getTimeout(), TimeUnit.MILLISECONDS, headers,
pushInfo.isClose()),
handler);
return new Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Push streams never send a reply
throw new UnsupportedOperationException();
}
@Override
public void onHeaders(Stream stream, HeadersInfo headersInfo)
{
throw new UnsupportedOperationException();
}
@Override
public void onData(Stream serverStream, final DataInfo serverDataInfo)
{
LOG.debug("S -> P pushed {} on {}", serverDataInfo, serverStream);
ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose())
{
@Override
public void consume(int delta)
{
super.consume(delta);
serverDataInfo.consume(delta);
}
};
StreamHandler handler = (StreamHandler)serverStream.getAttribute(STREAM_HANDLER_ATTRIBUTE);
handler.data(clientDataInfo);
}
};
}
@Override
public void onReply(final Stream stream, ReplyInfo replyInfo)
{
@ -304,30 +366,30 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
}
/**
* <p>{@link StreamHandler} implements the forwarding of DATA frames from the client to the server.</p>
* <p>Instances of this class buffer DATA frames sent by clients and send them to the server.
* The buffering happens between the send of the SYN_STREAM to the server (where DATA frames may arrive
* from the client before the SYN_STREAM has been fully sent), and between DATA frames, if the client
* is a fast producer and the server a slow consumer, or if the client is a SPDY v2 client (and hence
* without flow control) while the server is a SPDY v3 server (and hence with flow control).</p>
* <p>{@link StreamHandler} implements the forwarding of DATA frames from the client to the server.</p> <p>Instances
* of this class buffer DATA frames sent by clients and send them to the server. The buffering happens between the
* send of the SYN_STREAM to the server (where DATA frames may arrive from the client before the SYN_STREAM has been
* fully sent), and between DATA frames, if the client is a fast producer and the server a slow consumer, or if the
* client is a SPDY v2 client (and hence without flow control) while the server is a SPDY v3 server (and hence with
* flow control).</p>
*/
private class StreamHandler implements Promise<Stream>
{
private final Queue<DataInfoHandler> queue = new LinkedList<>();
private final Stream clientStream;
private final SynInfo serverSynInfo;
private final Info info;
private Stream serverStream;
private StreamHandler(Stream clientStream, SynInfo serverSynInfo)
private StreamHandler(Stream clientStream, Info info)
{
this.clientStream = clientStream;
this.serverSynInfo = serverSynInfo;
this.info = info;
}
@Override
public void succeeded(Stream serverStream)
{
LOG.debug("P -> S {} from {} to {}", serverSynInfo, clientStream, serverStream);
LOG.debug("P -> S {} from {} to {}", info, clientStream, serverStream);
serverStream.setAttribute(CLIENT_STREAM_ATTRIBUTE, clientStream);
@ -449,26 +511,8 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
}
}
private class ProxySessionFrameListener extends SessionFrameListener.Adapter implements StreamFrameListener
private class ProxySessionFrameListener extends SessionFrameListener.Adapter
{
@Override
public StreamFrameListener onSyn(Stream serverStream, SynInfo serverSynInfo)
{
LOG.debug("S -> P pushed {} on {}", serverSynInfo, serverStream);
Fields headers = new Fields(serverSynInfo.getHeaders(), false);
addResponseProxyHeaders(serverStream, headers);
customizeResponseHeaders(serverStream, headers);
Stream clientStream = (Stream)serverStream.getAssociatedStream().getAttribute(CLIENT_STREAM_ATTRIBUTE);
convert(serverStream.getSession().getVersion(), clientStream.getSession().getVersion(), headers);
StreamHandler handler = new StreamHandler(clientStream, serverSynInfo);
serverStream.setAttribute(STREAM_HANDLER_ATTRIBUTE, handler);
clientStream.push(new PushInfo(getTimeout(), TimeUnit.MILLISECONDS, headers, serverSynInfo.isClose()),
handler);
return this;
}
@Override
public void onRst(Session serverSession, RstInfo serverRstInfo)
@ -487,41 +531,9 @@ public class SPDYProxyEngine extends ProxyEngine implements StreamFrameListener
}
@Override
public void onGoAway(Session serverSession, GoAwayReceivedInfo goAwayReceivedInfo)
public void onGoAway(Session serverSession, GoAwayResultInfo goAwayResultInfo)
{
serverSessions.values().remove(serverSession);
}
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
// Push streams never send a reply
throw new UnsupportedOperationException();
}
@Override
public void onHeaders(Stream stream, HeadersInfo headersInfo)
{
throw new UnsupportedOperationException();
}
@Override
public void onData(Stream serverStream, final DataInfo serverDataInfo)
{
LOG.debug("S -> P pushed {} on {}", serverDataInfo, serverStream);
ByteBufferDataInfo clientDataInfo = new ByteBufferDataInfo(serverDataInfo.asByteBuffer(false), serverDataInfo.isClose())
{
@Override
public void consume(int delta)
{
super.consume(delta);
serverDataInfo.consume(delta);
}
};
StreamHandler handler = (StreamHandler)serverStream.getAttribute(STREAM_HANDLER_ATTRIBUTE);
handler.data(clientDataInfo);
}
}
}

View File

@ -18,16 +18,12 @@
package org.eclipse.jetty.spdy.server.http;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -37,6 +33,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.SPDY;
@ -50,10 +47,15 @@ import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
{
private final int referrerPushPeriod = 1000;
@ -107,39 +109,14 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
sendMainRequestAndCSSRequest();
final CountDownLatch pushDataLatch = new CountDownLatch(1);
final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
Session session = startClient(version, serverAddress, new SessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
validateHeaders(synInfo.getHeaders(), pushSynHeadersValid);
assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("URI header ends with css", synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
.value().endsWith
("" +
".css"),
is(true));
stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
return new StreamFrameListener.Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
dataInfo.consume(dataInfo.length());
pushDataLatch.countDown();
}
};
}
});
Session session = startClient(version, serverAddress, null);
// Send main request. That should initiate the push push's which get reset by the client
sendRequest(session, mainRequestHeaders);
sendRequest(session, mainRequestHeaders, pushSynHeadersValid, pushDataLatch);
assertThat("No push data is received", pushDataLatch.await(1, TimeUnit.SECONDS), is(false));
assertThat("Push push headers valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS), is(true));
sendRequest(session, associatedCSSRequestHeaders);
sendRequest(session, associatedCSSRequestHeaders, pushSynHeadersValid, pushDataLatch);
}
@Test
@ -157,7 +134,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
// Sleep for pushPeriod This should prevent application.js from being mapped as pushResource
Thread.sleep(referrerPushPeriod + 1);
sendRequest(session, associatedJSRequestHeaders);
sendRequest(session, associatedJSRequestHeaders, null, null);
run2ndClientRequests(false, true);
}
@ -171,7 +148,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
Session session = sendMainRequestAndCSSRequest();
sendRequest(session, associatedJSRequestHeaders);
sendRequest(session, associatedJSRequestHeaders, null, null);
run2ndClientRequests(false, true);
}
@ -200,18 +177,43 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
{
Session session = startClient(version, serverAddress, null);
sendRequest(session, mainRequestHeaders);
sendRequest(session, associatedCSSRequestHeaders);
sendRequest(session, mainRequestHeaders, null, null);
sendRequest(session, associatedCSSRequestHeaders, null, null);
return session;
}
private void sendRequest(Session session, Fields requestHeaders) throws InterruptedException
private void sendRequest(Session session, Fields requestHeaders, final CountDownLatch pushSynHeadersValid,
final CountDownLatch pushDataLatch) throws InterruptedException
{
final CountDownLatch dataReceivedLatch = new CountDownLatch(1);
final CountDownLatch received200OKLatch = new CountDownLatch(1);
session.syn(new SynInfo(requestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
.value().endsWith
("" +
".css"),
is(true));
stream.getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
return new StreamFrameListener.Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
dataInfo.consume(dataInfo.length());
pushDataLatch.countDown();
}
};
}
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -238,16 +240,17 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
final CountDownLatch pushDataLatch = new CountDownLatch(1);
final CountDownLatch pushSynHeadersValid = new CountDownLatch(1);
Session session2 = startClient(version, serverAddress, new SessionFrameListener.Adapter()
Session session2 = startClient(version, serverAddress, null);
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
if (validateHeaders)
validateHeaders(synInfo.getHeaders(), pushSynHeadersValid);
validateHeaders(pushInfo.getHeaders(), pushSynHeadersValid);
assertThat("Stream is unidirectional", stream.isUnidirectional(), is(true));
assertThat("URI header ends with css", synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
assertThat("URI header ends with css", pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version))
.value().endsWith
("" +
".css"),
@ -264,9 +267,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
}
};
}
});
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -292,6 +293,8 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
assertThat("Push push headers valid", pushSynHeadersValid.await(5, TimeUnit.SECONDS), is(true));
}
private static final Logger LOG = Log.getLogger(ReferrerPushStrategyTest.class);
@Test
public void testAssociatedResourceIsPushed() throws Exception
{
@ -326,16 +329,17 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
});
Assert.assertTrue(mainResourceLatch.await(5, TimeUnit.SECONDS));
sendRequest(session1, createHeaders(cssResource));
sendRequest(session1, createHeaders(cssResource), null, null);
// Create another client, and perform the same request for the main resource, we expect the css being pushed
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
final CountDownLatch pushDataLatch = new CountDownLatch(1);
Session session2 = startClient(version, address, new SessionFrameListener.Adapter()
Session session2 = startClient(version, address, null);
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
Assert.assertTrue(stream.isUnidirectional());
return new StreamFrameListener.Adapter()
@ -349,9 +353,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
}
};
}
});
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -452,13 +454,15 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
final CountDownLatch pushDataLatch = new CountDownLatch(1);
Session session2 = startClient(version, address, new SessionFrameListener.Adapter()
Session session2 = startClient(version, address, null);
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
Assert.assertTrue(stream.isUnidirectional());
Assert.assertTrue(synInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value().endsWith(".css"));
Assert.assertTrue(pushInfo.getHeaders().get(HTTPSPDYHeader.URI.name(version)).value().endsWith("" +
".css"));
return new StreamFrameListener.Adapter()
{
@Override
@ -470,9 +474,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
}
};
}
});
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -563,14 +565,31 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
final CountDownLatch mainStreamLatch = new CountDownLatch(2);
final CountDownLatch pushDataLatch = new CountDownLatch(2);
Session session2 = startClient(version, address, new SessionFrameListener.Adapter()
Session session2 = startClient(version, address, null);
LOG.warn("REQUEST FOR PUSHED RESOURCES");
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
Assert.assertTrue(stream.isUnidirectional());
return new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
return new Adapter()
{
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
dataInfo.consume(dataInfo.length());
if (dataInfo.isClose())
pushDataLatch.countDown();
}
};
}
@Override
public void onData(Stream stream, DataInfo dataInfo)
{
@ -580,9 +599,7 @@ public class ReferrerPushStrategyTest extends AbstractHTTPSPDYTest
}
};
}
});
session2.syn(new SynInfo(mainRequestHeaders, true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{

View File

@ -33,7 +33,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
@ -164,7 +164,7 @@ public class ProxyHTTPToSPDYTest
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
closeLatch.countDown();
}

View File

@ -37,7 +37,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.PingInfo;
import org.eclipse.jetty.spdy.api.PingResultInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
@ -429,7 +429,7 @@ public class ProxySPDYToHTTPTest
Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayReceivedInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayReceivedInfo)
{
goAwayLatch.countDown();
}

View File

@ -281,10 +281,16 @@ public class ProxySPDYToSPDYTest
final CountDownLatch pushSynLatch = new CountDownLatch(1);
final CountDownLatch pushDataLatch = new CountDownLatch(1);
Session client = factory.newSPDYClient(version).connect(proxyAddress, new SessionFrameListener.Adapter()
Session client = factory.newSPDYClient(version).connect(proxyAddress, null).get(5, TimeUnit.SECONDS);
Fields headers = new Fields();
headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
pushSynLatch.countDown();
return new StreamFrameListener.Adapter()
@ -298,14 +304,7 @@ public class ProxySPDYToSPDYTest
}
};
}
}).get(5, TimeUnit.SECONDS);
Fields headers = new Fields();
headers.put(HTTPSPDYHeader.HOST.name(version), "localhost:" + proxyAddress.getPort());
final CountDownLatch replyLatch = new CountDownLatch(1);
final CountDownLatch dataLatch = new CountDownLatch(1);
client.syn(new SynInfo(headers, true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{

View File

@ -35,7 +35,7 @@ import org.eclipse.jetty.spdy.StandardCompressionFactory;
import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
@ -211,7 +211,7 @@ public class ClosedStreamTest extends AbstractTest
};
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
goAwayReceivedLatch.countDown();
}

View File

@ -27,7 +27,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
@ -61,7 +61,7 @@ public class GoAwayTest extends AbstractTest
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
Assert.assertEquals(0, goAwayInfo.getLastStreamId());
Assert.assertSame(SessionStatus.OK, goAwayInfo.getSessionStatus());
@ -90,12 +90,12 @@ public class GoAwayTest extends AbstractTest
return null;
}
};
final AtomicReference<GoAwayReceivedInfo> ref = new AtomicReference<>();
final AtomicReference<GoAwayResultInfo> ref = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
ref.set(goAwayInfo);
latch.countDown();
@ -106,10 +106,10 @@ public class GoAwayTest extends AbstractTest
Stream stream1 = session.syn(new SynInfo(5, TimeUnit.SECONDS, new Fields(), true, (byte)0), null);
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
GoAwayReceivedInfo goAwayReceivedInfo = ref.get();
Assert.assertNotNull(goAwayReceivedInfo);
Assert.assertEquals(stream1.getId(), goAwayReceivedInfo.getLastStreamId());
Assert.assertSame(SessionStatus.OK, goAwayReceivedInfo.getSessionStatus());
GoAwayResultInfo goAwayResultInfo = ref.get();
Assert.assertNotNull(goAwayResultInfo);
Assert.assertEquals(stream1.getId(), goAwayResultInfo.getLastStreamId());
Assert.assertSame(SessionStatus.OK, goAwayResultInfo.getSessionStatus());
}
@Test
@ -139,7 +139,7 @@ public class GoAwayTest extends AbstractTest
SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
session.syn(new SynInfo(new Fields(), true), null, new FuturePromise<Stream>());
}
@ -184,12 +184,12 @@ public class GoAwayTest extends AbstractTest
}
}
};
final AtomicReference<GoAwayReceivedInfo> goAwayRef = new AtomicReference<>();
final AtomicReference<GoAwayResultInfo> goAwayRef = new AtomicReference<>();
final CountDownLatch goAwayLatch = new CountDownLatch(1);
SessionFrameListener clientSessionFrameListener = new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
goAwayRef.set(goAwayInfo);
goAwayLatch.countDown();
@ -228,7 +228,7 @@ public class GoAwayTest extends AbstractTest
// The last good stream is the second, because it was received by the server
Assert.assertTrue(goAwayLatch.await(5, TimeUnit.SECONDS));
GoAwayReceivedInfo goAway = goAwayRef.get();
GoAwayResultInfo goAway = goAwayRef.get();
Assert.assertNotNull(goAway);
Assert.assertEquals(stream2.getId(), goAway.getLastStreamId());
}

View File

@ -23,7 +23,7 @@ import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.api.Session;
@ -63,7 +63,7 @@ public class IdleTimeoutTest extends AbstractTest
Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
latch.countDown();
}
@ -85,7 +85,7 @@ public class IdleTimeoutTest extends AbstractTest
Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
latch.countDown();
}
@ -125,7 +125,7 @@ public class IdleTimeoutTest extends AbstractTest
Session session = startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
goAwayLatch.countDown();
}
@ -161,7 +161,7 @@ public class IdleTimeoutTest extends AbstractTest
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
latch.countDown();
}
@ -187,7 +187,7 @@ public class IdleTimeoutTest extends AbstractTest
InetSocketAddress address = startServer(new ServerSessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
latch.countDown();
}
@ -220,7 +220,7 @@ public class IdleTimeoutTest extends AbstractTest
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
latch.countDown();
}

View File

@ -19,12 +19,6 @@
package org.eclipse.jetty.spdy.server;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@ -44,7 +38,7 @@ import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.spdy.StandardCompressionFactory;
import org.eclipse.jetty.spdy.api.BytesDataInfo;
import org.eclipse.jetty.spdy.api.DataInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.PushInfo;
import org.eclipse.jetty.spdy.api.ReplyInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
@ -75,6 +69,12 @@ import org.eclipse.jetty.util.log.Logger;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
public class PushStreamTest extends AbstractTest
{
private static final Logger LOG = Log.getLogger(PushStreamTest.class);
@ -94,10 +94,12 @@ public class PushStreamTest extends AbstractTest
stream.push(new PushInfo(new Fields(), true), new Promise.Adapter<Stream>());
return null;
}
}), new SessionFrameListener.Adapter()
}), null);
Stream stream = clientSession.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
assertThat("streamId is even", stream.getId() % 2, is(0));
assertThat("stream is unidirectional", stream.isUnidirectional(), is(true));
@ -117,8 +119,6 @@ public class PushStreamTest extends AbstractTest
return null;
}
});
Stream stream = clientSession.syn(new SynInfo(new Fields(), true), null);
assertThat("onSyn has been called", pushStreamLatch.await(5, TimeUnit.SECONDS), is(true));
Stream pushStream = pushStreamRef.get();
assertThat("main stream and associated stream are the same", stream, sameInstance(pushStream.getAssociatedStream()));
@ -177,10 +177,12 @@ public class PushStreamTest extends AbstractTest
}
}
}), new SessionFrameListener.Adapter()
}), null);
Stream stream = clientSession.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
pushStreamSynLatch.countDown();
return new StreamFrameListener.Adapter()
@ -193,10 +195,7 @@ public class PushStreamTest extends AbstractTest
}
};
}
});
Stream stream = clientSession.syn(new SynInfo(new Fields(), false), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -298,10 +297,12 @@ public class PushStreamTest extends AbstractTest
throw new IllegalStateException(e);
}
}
}), new SessionFrameListener.Adapter()
}), null);
Stream stream = clientSession.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
return new StreamFrameListener.Adapter()
{
@ -327,10 +328,7 @@ public class PushStreamTest extends AbstractTest
}
};
}
});
Stream stream = clientSession.syn(new SynInfo(new Fields(), true), new StreamFrameListener.Adapter()
{
@Override
public void onReply(Stream stream, ReplyInfo replyInfo)
{
@ -427,7 +425,7 @@ public class PushStreamTest extends AbstractTest
}
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayInfo)
{
goAwayReceivedLatch.countDown();
}
@ -543,20 +541,14 @@ public class PushStreamTest extends AbstractTest
stream.push(new PushInfo(new Fields(), false), new Promise.Adapter<Stream>());
return null;
}
}), new SessionFrameListener.Adapter()
{
@Override
public StreamFrameListener onSyn(Stream stream, SynInfo synInfo)
{
assertStreamIdIsEven(stream);
pushStreamIdIsEvenLatch.countDown();
return super.onSyn(stream, synInfo);
}
});
}), null);
Stream stream = clientSession.syn(new SynInfo(new Fields(), false), null);
Stream stream2 = clientSession.syn(new SynInfo(new Fields(), false), null);
Stream stream3 = clientSession.syn(new SynInfo(new Fields(), false), null);
Stream stream = clientSession.syn(new SynInfo(new Fields(), false),
new VerifyPushStreamIdIsEvenStreamFrameListener(pushStreamIdIsEvenLatch));
Stream stream2 = clientSession.syn(new SynInfo(new Fields(), false),
new VerifyPushStreamIdIsEvenStreamFrameListener(pushStreamIdIsEvenLatch));
Stream stream3 = clientSession.syn(new SynInfo(new Fields(), false),
new VerifyPushStreamIdIsEvenStreamFrameListener(pushStreamIdIsEvenLatch));
assertStreamIdIsOdd(stream);
assertStreamIdIsOdd(stream2);
assertStreamIdIsOdd(stream3);
@ -564,6 +556,24 @@ public class PushStreamTest extends AbstractTest
assertThat("all pushStreams had even ids", pushStreamIdIsEvenLatch.await(5, TimeUnit.SECONDS), is(true));
}
private class VerifyPushStreamIdIsEvenStreamFrameListener extends StreamFrameListener.Adapter
{
final CountDownLatch pushStreamIdIsEvenLatch;
private VerifyPushStreamIdIsEvenStreamFrameListener(CountDownLatch pushStreamIdIsEvenLatch)
{
this.pushStreamIdIsEvenLatch = pushStreamIdIsEvenLatch;
}
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
assertStreamIdIsEven(stream);
pushStreamIdIsEvenLatch.countDown();
return super.onPush(stream, pushInfo);
}
}
private void assertStreamIdIsEven(Stream stream)
{
assertThat("streamId is odd", stream.getId() % 2, is(0));

View File

@ -23,7 +23,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.junit.Assert;
@ -38,7 +38,7 @@ public class SPDYClientFactoryTest extends AbstractTest
startClient(startServer(new ServerSessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayReceivedInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
{
latch.countDown();
}

View File

@ -23,7 +23,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.GoAwayReceivedInfo;
import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.SessionFrameListener;
import org.junit.Assert;
@ -38,7 +38,7 @@ public class SPDYServerConnectorTest extends AbstractTest
startClient(startServer(null), new SessionFrameListener.Adapter()
{
@Override
public void onGoAway(Session session, GoAwayReceivedInfo goAwayReceivedInfo)
public void onGoAway(Session session, GoAwayResultInfo goAwayResultInfo)
{
latch.countDown();
}

View File

@ -45,6 +45,16 @@
# These control what classes are on the classpath
# for a full listing do
# java -jar start.jar --list-options
#
# Enable classpath OPTIONS. Each options represents one or more jars
# to be added to the classpath. The options can be listed with --help
# or --list-options.
# By convention, options starting with a capital letter (eg Server)
# are aggregations of other available options.
# Directories in $JETTY_HOME/lib can be added as dynamic OPTIONS by
# convention. E.g. put some logging jars in $JETTY_HOME/lib/logging
# and make them available in the classpath by adding a "logging" OPTION
# like so: OPTIONS=Server,jsp,logging
#-----------------------------------------------------------
OPTIONS=Server,jsp,resources,websocket,ext
#-----------------------------------------------------------

View File

@ -0,0 +1,175 @@
//
// ========================================================================
// Copyright (c) 1995-2013 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.util;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
/**
* Creates asynchronously {@link SocketAddress} instances, returning them through a {@link Promise},
* in order to avoid blocking on DNS lookup.
* <p />
* {@link InetSocketAddress#InetSocketAddress(String, int)} attempts to perform a DNS resolution of
* the host name, and this may block for several seconds.
* This class creates the {@link InetSocketAddress} in a separate thread and provides the result
* through a {@link Promise}, with the possibility to specify a timeout for the operation.
* <p />
* Example usage:
* <pre>
* SocketAddressResolver resolver = new SocketAddressResolver(executor, scheduler);
* resolver.resolve("www.google.com", 80, new Promise&lt;SocketAddress&gt;()
* {
* public void succeeded(SocketAddress result)
* {
* // The address was resolved
* }
*
* public void failed(Throwable failure)
* {
* // The address resolution failed
* }
* });
* </pre>
*/
public class SocketAddressResolver
{
private static final Logger LOG = Log.getLogger(SocketAddressResolver.class);
private final Executor executor;
private final Scheduler scheduler;
private final long timeout;
/**
* Creates a new instance with the given executor (to perform DNS resolution in a separate thread),
* the given scheduler (to cancel the operation if it takes too long) and the given timeout, in milliseconds.
*
* @param executor the thread pool to use to perform DNS resolution in pooled threads
* @param scheduler the scheduler to schedule tasks to cancel DNS resolution if it takes too long
* @param timeout the timeout, in milliseconds, for the DNS resolution to complete
*/
public SocketAddressResolver(Executor executor, Scheduler scheduler, long timeout)
{
this.executor = executor;
this.scheduler = scheduler;
this.timeout = timeout;
}
public Executor getExecutor()
{
return executor;
}
public Scheduler getScheduler()
{
return scheduler;
}
public long getTimeout()
{
return timeout;
}
/**
* Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
* with the default timeout.
*
* @param host the host to resolve
* @param port the port of the resulting socket address
* @param promise the callback invoked when the resolution succeeds or fails
* @see #resolve(String, int, long, Promise)
*/
public void resolve(String host, int port, Promise<SocketAddress> promise)
{
resolve(host, port, timeout, promise);
}
/**
* Resolves the given host and port, returning a {@link SocketAddress} through the given {@link Promise}
* with the given timeout.
*
* @param host the host to resolve
* @param port the port of the resulting socket address
* @param timeout the timeout, in milliseconds, for the DNS resolution to complete
* @param promise the callback invoked when the resolution succeeds or fails
*/
protected void resolve(final String host, final int port, final long timeout, final Promise<SocketAddress> promise)
{
executor.execute(new Runnable()
{
@Override
public void run()
{
Scheduler.Task task = null;
final AtomicBoolean complete = new AtomicBoolean();
if (timeout > 0)
{
final Thread thread = Thread.currentThread();
task = scheduler.schedule(new Runnable()
{
@Override
public void run()
{
if (complete.compareAndSet(false, true))
{
promise.failed(new TimeoutException());
thread.interrupt();
}
}
}, timeout, TimeUnit.MILLISECONDS);
}
try
{
long start = System.nanoTime();
InetSocketAddress result = new InetSocketAddress(host, port);
long elapsed = System.nanoTime() - start;
LOG.debug("Resolved {} in {} ms", host, TimeUnit.NANOSECONDS.toMillis(elapsed));
if (complete.compareAndSet(false, true))
{
if (result.isUnresolved())
promise.failed(new UnresolvedAddressException());
else
promise.succeeded(result);
}
}
catch (Throwable x)
{
if (complete.compareAndSet(false, true))
promise.failed(x);
}
finally
{
if (task != null)
task.cancel();
// Reset the interrupted status before releasing the thread to the pool
Thread.interrupted();
}
}
});
}
}

View File

@ -65,6 +65,19 @@ class JarFileResource extends JarResource
_list=null;
_entry=null;
_file=null;
if ( _jarFile != null )
{
try
{
_jarFile.close();
}
catch ( IOException ioe )
{
LOG.ignore(ioe);
}
}
_jarFile=null;
super.release();
}
@ -303,12 +316,11 @@ class JarFileResource extends JarResource
throw new IllegalStateException();
}
Enumeration e=jarFile.entries();
Enumeration<JarEntry> e=jarFile.entries();
String dir=_urlString.substring(_urlString.indexOf("!/")+2);
while(e.hasMoreElements())
{
JarEntry entry = (JarEntry) e.nextElement();
JarEntry entry = e.nextElement();
String name=entry.getName().replace('\\','/');
if(!name.startsWith(dir) || name.length()==dir.length())
{

View File

@ -27,6 +27,10 @@ import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** A scheduler based on the the JVM Timer class
*/
public class TimerScheduler extends AbstractLifeCycle implements Scheduler, Runnable
{
private static final Logger LOG = Log.getLogger(TimerScheduler.class);

View File

@ -32,8 +32,13 @@ import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;
import junit.framework.Assert;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.IO;
@ -121,11 +126,15 @@ public class ResourceTest
file=new File(file.getCanonicalPath());
URI uri = file.toURI();
__userURL=uri.toURL();
__userURL = new URL(__userURL.toString() + "src/test/resources/org/eclipse/jetty/util/resource/");
FilePermission perm = (FilePermission) __userURL.openConnection().getPermission();
__userDir = new File(perm.getName()).getCanonicalPath() + File.separatorChar;
__relDir = "src/test/resources/org/eclipse/jetty/util/resource/".replace('/', File.separatorChar);
__userURL = MavenTestingUtils.getTestResourcesDir().toURI().toURL();
FilePermission perm = (FilePermission) __userURL.openConnection().getPermission();
__userDir = new File(perm.getName()).getCanonicalPath() + File.separatorChar;
__relDir = "src/test/resources/".replace('/', File.separatorChar);
//System.err.println("User Dir="+__userDir);
//System.err.println("Rel Dir="+__relDir);
//System.err.println("User URL="+__userURL);
tmpFile=File.createTempFile("test",null).getCanonicalFile();
tmpFile.deleteOnExit();
@ -319,14 +328,16 @@ public class ResourceTest
throws Exception
{
String s = "jar:"+__userURL+"TestData/test.zip!/subdir/numbers";
ZipFile zf = new ZipFile(MavenTestingUtils.getProjectFile("src/test/resources/org/eclipse/jetty/util/resource/TestData/test.zip"));
ZipFile zf = new ZipFile(MavenTestingUtils.getTestResourceFile("TestData/test.zip"));
long last = zf.getEntry("subdir/numbers").getTime();
Resource r = Resource.newResource(s);
assertEquals(last,r.lastModified());
}
/* ------------------------------------------------------------ */
@Test
public void testJarFileCopyToDirectoryTraversal () throws Exception

View File

@ -60,10 +60,14 @@ public abstract class Descriptor
if (_root == null)
{
//boolean oldValidating = _processor.getParser().getValidating();
//_processor.getParser().setValidating(_validating);
_root = _parser.parse(_xml.getURL().toString());
//_processor.getParser().setValidating(oldValidating);
try
{
_root = _parser.parse(_xml.getInputStream());
}
finally
{
_xml.release();
}
}
}

View File

@ -29,12 +29,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.NoJspServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
@ -44,7 +42,6 @@ import org.eclipse.jetty.util.log.Logger;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@ -65,16 +62,22 @@ public class JspAndDefaultWithAliasesTest
public static Collection<String[]> data()
{
List<String[]> data = new ArrayList<String[]>();
double javaVersion = Double.parseDouble(System.getProperty("java.specification.version"));
// @formatter:off
data.add(new String[] { "false","/dump.jsp" });
data.add(new String[] { "true", "/dump.jsp%00" });
data.add(new String[] { "false","/dump.jsp%00x" });
data.add(new String[] { "false","/dump.jsp%00/" });
data.add(new String[] { "false","/dump.jsp%00x/" });
data.add(new String[] { "false","/dump.jsp%00x/dump.jsp" });
data.add(new String[] { "false","/dump.jsp%00/dump.jsp" });
data.add(new String[] { "false","/dump.jsp%00/index.html" });
if (javaVersion >= 1.7)
{
data.add(new String[] { "false","/dump.jsp%00x" });
data.add(new String[] { "false","/dump.jsp%00x/" });
data.add(new String[] { "false","/dump.jsp%00/index.html" });
}
// @formatter:on
return data;

View File

@ -29,7 +29,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.jasper.servlet.JspServlet;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
@ -64,16 +63,22 @@ public class JspAndDefaultWithoutAliasesTest
public static Collection<Object[]> data()
{
List<Object[]> data = new ArrayList<Object[]>();
double javaVersion = Double.parseDouble(System.getProperty("java.specification.version"));
// @formatter:off
data.add(new Object[] { "/dump.jsp" });
data.add(new Object[] { "/dump.jsp%00" });
data.add(new Object[] { "/dump.jsp%00x" });
data.add(new Object[] { "/dump.jsp%00/" });
data.add(new Object[] { "/dump.jsp%00x/" });
data.add(new Object[] { "/dump.jsp%00x/dump.jsp" });
data.add(new Object[] { "/dump.jsp%00/dump.jsp" });
data.add(new Object[] { "/dump.jsp%00/index.html" });
if (javaVersion >= 1.7)
{
data.add(new Object[] { "/dump.jsp%00/" });
data.add(new Object[] { "/dump.jsp%00x/" });
}
// @formatter:on
return data;