Merge branch 'master' into javawebsocket-jsr
This commit is contained in:
commit
f2723404c6
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
*/
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) ||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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<Connection> futureConnection = new FuturePromise<>();
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -200,4 +200,5 @@ etc/jetty-requestlog.xml
|
|||
# etc/jetty-stats.xml
|
||||
# etc/jetty-debug.xml
|
||||
# etc/jetty-ipaccess.xml
|
||||
# etc/jetty-lowresources.xml
|
||||
#===========================================================
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -107,7 +107,7 @@ public class IncludableGzipFilterMinSizeTest
|
|||
|
||||
try {
|
||||
tester.start();
|
||||
tester.assertIsResponseGzipCompressed("big_script.js");
|
||||
tester.assertIsResponseGzipCompressed("GET","big_script.js");
|
||||
} finally {
|
||||
tester.stop();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -148,6 +148,7 @@ public class StandardStream implements IStream
|
|||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamFrameListener getStreamFrameListener()
|
||||
{
|
||||
return listener;
|
||||
|
|
|
@ -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;
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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:
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
#-----------------------------------------------------------
|
||||
|
|
|
@ -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<SocketAddress>()
|
||||
* {
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue