461499 - ConnectionPool may leak connections.
Made associate(), disassociate() and abort() atomic operations using the HttpExchange state to coordinate atomicity. In this way, it's not possible to associate a HttpChannel and a HttpExchange if the latter has been aborted.
This commit is contained in:
parent
3b31d6aa12
commit
1dc66b72dd
|
@ -18,74 +18,126 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.SpinLock;
|
||||
|
||||
public abstract class HttpChannel
|
||||
{
|
||||
protected static final Logger LOG = Log.getLogger(HttpChannel.class);
|
||||
|
||||
private final AtomicReference<HttpExchange> exchange = new AtomicReference<>();
|
||||
private final HttpDestination destination;
|
||||
private final SpinLock _lock = new SpinLock();
|
||||
private final HttpDestination _destination;
|
||||
private HttpExchange _exchange;
|
||||
|
||||
protected HttpChannel(HttpDestination destination)
|
||||
{
|
||||
this.destination = destination;
|
||||
this._destination = destination;
|
||||
}
|
||||
|
||||
public HttpDestination getHttpDestination()
|
||||
{
|
||||
return destination;
|
||||
return _destination;
|
||||
}
|
||||
|
||||
public void associate(HttpExchange exchange)
|
||||
/**
|
||||
* <p>Associates the given {@code exchange} to this channel in order to be sent over the network.</p>
|
||||
* <p>If the association is successful, the exchange can be sent. Otherwise, the channel must be
|
||||
* disposed because whoever terminated the exchange did not do it - it did not have the channel yet.</p>
|
||||
*
|
||||
* @param exchange the exchange to associate
|
||||
* @return true if the association was successful, false otherwise
|
||||
*/
|
||||
public boolean associate(HttpExchange exchange)
|
||||
{
|
||||
if (this.exchange.compareAndSet(null, exchange))
|
||||
boolean result = false;
|
||||
boolean abort = true;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
exchange.associate(this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} associated to {}", exchange, this);
|
||||
if (_exchange == null)
|
||||
{
|
||||
abort = false;
|
||||
result = exchange.associate(this);
|
||||
if (result)
|
||||
_exchange = exchange;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
if (abort)
|
||||
exchange.getRequest().abort(new UnsupportedOperationException("Pipelined requests not supported"));
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} associated {} to {}", exchange, result, this);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public HttpExchange disassociate()
|
||||
public boolean disassociate(HttpExchange exchange)
|
||||
{
|
||||
HttpExchange exchange = this.exchange.getAndSet(null);
|
||||
if (exchange != null)
|
||||
exchange.disassociate(this);
|
||||
boolean result = false;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
HttpExchange existing = _exchange;
|
||||
_exchange = null;
|
||||
if (existing == exchange)
|
||||
{
|
||||
existing.disassociate(this);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} disassociated from {}", exchange, this);
|
||||
return exchange;
|
||||
LOG.debug("{} disassociated {} from {}", exchange, result, this);
|
||||
return result;
|
||||
}
|
||||
|
||||
public HttpExchange getHttpExchange()
|
||||
{
|
||||
return exchange.get();
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
return _exchange;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract HttpSender getHttpSender();
|
||||
|
||||
protected abstract HttpReceiver getHttpReceiver();
|
||||
|
||||
public abstract void send();
|
||||
|
||||
public abstract void proceed(HttpExchange exchange, Throwable failure);
|
||||
public abstract void release();
|
||||
|
||||
public abstract boolean abort(Throwable cause);
|
||||
|
||||
public abstract boolean abortResponse(Throwable cause);
|
||||
|
||||
public void exchangeTerminated(Result result)
|
||||
public void proceed(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
disassociate();
|
||||
getHttpSender().proceed(exchange, failure);
|
||||
}
|
||||
|
||||
public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure)
|
||||
{
|
||||
boolean requestAborted = false;
|
||||
if (requestFailure != null)
|
||||
requestAborted = getHttpSender().abort(exchange, requestFailure);
|
||||
|
||||
boolean responseAborted = false;
|
||||
if (responseFailure != null)
|
||||
responseAborted = abortResponse(exchange, responseFailure);
|
||||
|
||||
return requestAborted || responseAborted;
|
||||
}
|
||||
|
||||
public boolean abortResponse(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
return getHttpReceiver().abort(exchange, failure);
|
||||
}
|
||||
|
||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||
{
|
||||
disassociate(exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x(exchange=%s)", getClass().getSimpleName(), hashCode(), exchange);
|
||||
return String.format("%s@%x(exchange=%s)", getClass().getSimpleName(), hashCode(), getHttpExchange());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
|
@ -31,21 +30,14 @@ public class HttpExchange
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(HttpExchange.class);
|
||||
|
||||
private final AtomicReference<HttpChannel> channel = new AtomicReference<>();
|
||||
private final HttpDestination destination;
|
||||
private final HttpRequest request;
|
||||
private final List<Response.ResponseListener> listeners;
|
||||
private final HttpResponse response;
|
||||
|
||||
enum State
|
||||
{
|
||||
PENDING, COMPLETED, TERMINATED
|
||||
}
|
||||
|
||||
;
|
||||
private final SpinLock _lock = new SpinLock();
|
||||
private State requestState = State.PENDING;
|
||||
private State responseState = State.PENDING;
|
||||
private HttpChannel _channel;
|
||||
private Throwable requestFailure;
|
||||
private Throwable responseFailure;
|
||||
|
||||
|
@ -96,120 +88,187 @@ public class HttpExchange
|
|||
}
|
||||
}
|
||||
|
||||
public void associate(HttpChannel channel)
|
||||
/**
|
||||
* <p>Associates the given {@code channel} to this exchange.</p>
|
||||
* <p>Works in strict collaboration with {@link HttpChannel#associate(HttpExchange)}.</p>
|
||||
*
|
||||
* @param channel the channel to associate to this exchange
|
||||
* @return true if the channel could be associated, false otherwise
|
||||
*/
|
||||
boolean associate(HttpChannel channel)
|
||||
{
|
||||
if (!this.channel.compareAndSet(null, channel))
|
||||
request.abort(new IllegalStateException());
|
||||
boolean result = false;
|
||||
boolean abort = false;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
// Only associate if the exchange state is initial,
|
||||
// as the exchange could be already failed.
|
||||
if (requestState == State.PENDING && responseState == State.PENDING)
|
||||
{
|
||||
abort = _channel != null;
|
||||
if (!abort)
|
||||
{
|
||||
_channel = channel;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disassociate(HttpChannel channel)
|
||||
{
|
||||
if (!this.channel.compareAndSet(channel, null))
|
||||
request.abort(new IllegalStateException());
|
||||
if (abort)
|
||||
request.abort(new IllegalStateException(toString()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean requestComplete()
|
||||
void disassociate(HttpChannel channel)
|
||||
{
|
||||
boolean abort = false;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
if (_channel != channel || requestState != State.TERMINATED || responseState != State.TERMINATED)
|
||||
abort = true;
|
||||
_channel = null;
|
||||
}
|
||||
|
||||
if (abort)
|
||||
request.abort(new IllegalStateException(toString()));
|
||||
}
|
||||
|
||||
private HttpChannel getHttpChannel()
|
||||
{
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
if (requestState != State.PENDING)
|
||||
return false;
|
||||
return _channel;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean requestComplete(Throwable failure)
|
||||
{
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
return completeRequest(failure);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean completeRequest(Throwable failure)
|
||||
{
|
||||
if (requestState == State.PENDING)
|
||||
{
|
||||
requestState = State.COMPLETED;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean responseComplete()
|
||||
{
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
if (responseState != State.PENDING)
|
||||
return false;
|
||||
responseState = State.COMPLETED;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Result terminateRequest(Throwable failure)
|
||||
{
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
requestState = State.TERMINATED;
|
||||
requestFailure = failure;
|
||||
if (State.TERMINATED.equals(responseState))
|
||||
return new Result(getRequest(), requestFailure, getResponse(), responseFailure);
|
||||
return true;
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public Result terminateResponse(Throwable failure)
|
||||
public boolean responseComplete(Throwable failure)
|
||||
{
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
responseState = State.TERMINATED;
|
||||
responseFailure = failure;
|
||||
if (requestState == State.TERMINATED)
|
||||
return new Result(getRequest(), requestFailure, getResponse(), responseFailure);
|
||||
return completeResponse(failure);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public boolean abort(Throwable cause)
|
||||
private boolean completeResponse(Throwable failure)
|
||||
{
|
||||
if (responseState == State.PENDING)
|
||||
{
|
||||
responseState = State.COMPLETED;
|
||||
responseFailure = failure;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Result terminateRequest()
|
||||
{
|
||||
Result result = null;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
if (requestState == State.COMPLETED)
|
||||
requestState = State.TERMINATED;
|
||||
if (requestState == State.TERMINATED && responseState == State.TERMINATED)
|
||||
result = new Result(getRequest(), requestFailure, getResponse(), responseFailure);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Terminated request for {}, result: {}", this, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Result terminateResponse()
|
||||
{
|
||||
Result result = null;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
if (responseState == State.COMPLETED)
|
||||
responseState = State.TERMINATED;
|
||||
if (requestState == State.TERMINATED && responseState == State.TERMINATED)
|
||||
result = new Result(getRequest(), requestFailure, getResponse(), responseFailure);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Terminated response for {}, result: {}", this, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean abort(Throwable failure)
|
||||
{
|
||||
// Atomically change the state of this exchange to be completed.
|
||||
// This will avoid that this exchange can be associated to a channel.
|
||||
boolean abortRequest;
|
||||
boolean abortResponse;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
abortRequest = completeRequest(failure);
|
||||
abortResponse = completeResponse(failure);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Failed {}: req={}/rsp={} {}", this, abortRequest, abortResponse, failure);
|
||||
|
||||
if (!abortRequest && !abortResponse)
|
||||
return false;
|
||||
|
||||
// We failed this exchange, deal with it.
|
||||
|
||||
// Case #1: exchange was in the destination queue.
|
||||
if (destination.remove(this))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aborting while queued {}: {}", this, cause);
|
||||
return fail(cause);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpChannel channel = this.channel.get();
|
||||
if (channel == null)
|
||||
return fail(cause);
|
||||
|
||||
boolean aborted = channel.abort(cause);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aborted ({}) while active {}: {}", aborted, this, cause);
|
||||
return aborted;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean fail(Throwable cause)
|
||||
{
|
||||
boolean notify = false;
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
if (requestState != State.TERMINATED)
|
||||
{
|
||||
requestState = State.TERMINATED;
|
||||
notify = true;
|
||||
requestFailure = cause;
|
||||
}
|
||||
if (responseState != State.TERMINATED)
|
||||
{
|
||||
responseState = State.TERMINATED;
|
||||
notify = true;
|
||||
responseFailure = cause;
|
||||
}
|
||||
}
|
||||
|
||||
if (notify)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Failing {}: {}", this, cause);
|
||||
destination.getRequestNotifier().notifyFailure(request, cause);
|
||||
List<Response.ResponseListener> listeners = getConversation().getResponseListeners();
|
||||
ResponseNotifier responseNotifier = destination.getResponseNotifier();
|
||||
responseNotifier.notifyFailure(listeners, response, cause);
|
||||
responseNotifier.notifyComplete(listeners, new Result(request, cause, response, cause));
|
||||
LOG.debug("Aborting while queued {}: {}", this, failure);
|
||||
notifyFailureComplete(failure);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
||||
HttpChannel channel = getHttpChannel();
|
||||
if (channel == null)
|
||||
{
|
||||
return false;
|
||||
// Case #2: exchange was not yet associated.
|
||||
// Because this exchange is failed, when associate() is called
|
||||
// it will return false, and the caller will dispose the channel.
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aborted before association {}: {}", this, failure);
|
||||
notifyFailureComplete(failure);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case #3: exchange was already associated.
|
||||
boolean aborted = channel.abort(this, abortRequest ? failure : null, abortResponse ? failure : null);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aborted ({}) while active {}: {}", aborted, this, failure);
|
||||
return aborted;
|
||||
}
|
||||
|
||||
private void notifyFailureComplete(Throwable failure)
|
||||
{
|
||||
destination.getRequestNotifier().notifyFailure(request, failure);
|
||||
List<Response.ResponseListener> listeners = getConversation().getResponseListeners();
|
||||
ResponseNotifier responseNotifier = destination.getResponseNotifier();
|
||||
responseNotifier.notifyFailure(listeners, response, failure);
|
||||
responseNotifier.notifyComplete(listeners, new Result(request, failure, response, failure));
|
||||
}
|
||||
|
||||
public void resetResponse()
|
||||
|
@ -223,7 +282,7 @@ public class HttpExchange
|
|||
|
||||
public void proceed(Throwable failure)
|
||||
{
|
||||
HttpChannel channel = this.channel.get();
|
||||
HttpChannel channel = getHttpChannel();
|
||||
if (channel != null)
|
||||
channel.proceed(this, failure);
|
||||
}
|
||||
|
@ -233,11 +292,16 @@ public class HttpExchange
|
|||
{
|
||||
try (SpinLock.Lock lock = _lock.lock())
|
||||
{
|
||||
return String.format("%s@%x req=%s/%s res=%s/%s",
|
||||
return String.format("%s@%x req=%s/%s@%h res=%s/%s@%h",
|
||||
HttpExchange.class.getSimpleName(),
|
||||
hashCode(),
|
||||
requestState, requestFailure,
|
||||
responseState, responseFailure);
|
||||
requestState, requestFailure, requestFailure,
|
||||
responseState, responseFailure, responseFailure);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State
|
||||
{
|
||||
PENDING, COMPLETED, TERMINATED
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,10 +129,11 @@ public abstract class HttpReceiver
|
|||
ResponseNotifier notifier = destination.getResponseNotifier();
|
||||
notifier.notifyBegin(conversation.getResponseListeners(), response);
|
||||
|
||||
if (!updateResponseState(ResponseState.TRANSIENT, ResponseState.BEGIN))
|
||||
terminateResponse(exchange, failure);
|
||||
|
||||
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.BEGIN))
|
||||
return true;
|
||||
|
||||
terminateResponse(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -193,10 +194,11 @@ public abstract class HttpReceiver
|
|||
}
|
||||
}
|
||||
|
||||
if (!updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADER))
|
||||
terminateResponse(exchange, failure);
|
||||
|
||||
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADER))
|
||||
return true;
|
||||
|
||||
terminateResponse(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void storeCookie(URI uri, HttpField field)
|
||||
|
@ -269,10 +271,11 @@ public abstract class HttpReceiver
|
|||
}
|
||||
}
|
||||
|
||||
if (!updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADERS))
|
||||
terminateResponse(exchange, failure);
|
||||
|
||||
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.HEADERS))
|
||||
return true;
|
||||
|
||||
terminateResponse(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -343,10 +346,11 @@ public abstract class HttpReceiver
|
|||
}
|
||||
}
|
||||
|
||||
if (!updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT))
|
||||
terminateResponse(exchange, failure);
|
||||
|
||||
if (updateResponseState(ResponseState.TRANSIENT, ResponseState.CONTENT))
|
||||
return true;
|
||||
|
||||
terminateResponse(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,7 +366,7 @@ public abstract class HttpReceiver
|
|||
{
|
||||
// Mark atomically the response as completed, with respect
|
||||
// to concurrency between response success and response failure.
|
||||
boolean completed = exchange.responseComplete();
|
||||
boolean completed = exchange.responseComplete(null);
|
||||
if (!completed)
|
||||
return false;
|
||||
|
||||
|
@ -371,12 +375,13 @@ public abstract class HttpReceiver
|
|||
// Reset to be ready for another response.
|
||||
reset();
|
||||
|
||||
// Mark atomically the response as terminated and succeeded,
|
||||
// with respect to concurrency between request and response.
|
||||
Result result = exchange.terminateResponse(null);
|
||||
// Mark atomically the response as terminated, with
|
||||
// respect to concurrency between request and response.
|
||||
Result result = exchange.terminateResponse();
|
||||
|
||||
// It is important to notify *after* we reset and terminate
|
||||
// because the notification may trigger another request/response.
|
||||
// Notify *after* resetting and terminating, because
|
||||
// the notification may trigger another request/response,
|
||||
// or the reset of the response in case of 100-Continue.
|
||||
HttpResponse response = exchange.getResponse();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response success {}", response);
|
||||
|
@ -409,60 +414,15 @@ public abstract class HttpReceiver
|
|||
|
||||
// Mark atomically the response as completed, with respect
|
||||
// to concurrency between response success and response failure.
|
||||
boolean completed = exchange.responseComplete();
|
||||
if (!completed)
|
||||
if (exchange.responseComplete(failure))
|
||||
return abort(exchange, failure);
|
||||
|
||||
return false;
|
||||
|
||||
this.failure = failure;
|
||||
|
||||
// Update the state to avoid more response processing.
|
||||
boolean fail;
|
||||
while (true)
|
||||
{
|
||||
ResponseState current = responseState.get();
|
||||
if (updateResponseState(current, ResponseState.FAILURE))
|
||||
{
|
||||
fail = current != ResponseState.TRANSIENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dispose();
|
||||
|
||||
Result result = failResponse(exchange, failure);
|
||||
|
||||
if (fail)
|
||||
private void terminateResponse(HttpExchange exchange)
|
||||
{
|
||||
terminateResponse(exchange, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Concurrent failure: response termination skipped, performed by helpers");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Result failResponse(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
// Mark atomically the response as terminated and failed,
|
||||
// with respect to concurrency between request and response.
|
||||
Result result = exchange.terminateResponse(failure);
|
||||
|
||||
HttpResponse response = exchange.getResponse();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response failure {} {} on {}: {}", response, exchange, getHttpChannel(), failure);
|
||||
List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
|
||||
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
||||
notifier.notifyFailure(listeners, response, failure);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void terminateResponse(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
Result result = failResponse(exchange, failure);
|
||||
Result result = exchange.terminateResponse();
|
||||
terminateResponse(exchange, result);
|
||||
}
|
||||
|
||||
|
@ -477,14 +437,14 @@ public abstract class HttpReceiver
|
|||
{
|
||||
boolean ordered = getHttpDestination().getHttpClient().isStrictEventOrdering();
|
||||
if (!ordered)
|
||||
channel.exchangeTerminated(result);
|
||||
channel.exchangeTerminated(exchange, result);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request/Response {}: {}", failure == null ? "succeeded" : "failed", result);
|
||||
List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
|
||||
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
||||
notifier.notifyComplete(listeners, result);
|
||||
if (ordered)
|
||||
channel.exchangeTerminated(result);
|
||||
channel.exchangeTerminated(exchange, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -512,9 +472,56 @@ public abstract class HttpReceiver
|
|||
decoder = null;
|
||||
}
|
||||
|
||||
public boolean abort(Throwable cause)
|
||||
public boolean abort(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
return responseFailure(cause);
|
||||
// Update the state to avoid more response processing.
|
||||
boolean terminate;
|
||||
out: while (true)
|
||||
{
|
||||
ResponseState current = responseState.get();
|
||||
switch (current)
|
||||
{
|
||||
case FAILURE:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (updateResponseState(current, ResponseState.FAILURE))
|
||||
{
|
||||
terminate = current != ResponseState.TRANSIENT;
|
||||
break out;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.failure = failure;
|
||||
|
||||
dispose();
|
||||
|
||||
HttpResponse response = exchange.getResponse();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response failure {} {} on {}: {}", response, exchange, getHttpChannel(), failure);
|
||||
List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners();
|
||||
ResponseNotifier notifier = getHttpDestination().getResponseNotifier();
|
||||
notifier.notifyFailure(listeners, response, failure);
|
||||
|
||||
if (terminate)
|
||||
{
|
||||
// Mark atomically the response as terminated, with
|
||||
// respect to concurrency between request and response.
|
||||
Result result = exchange.terminateResponse();
|
||||
terminateResponse(exchange, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Concurrent failure: response termination skipped, performed by helpers");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean updateResponseState(ResponseState from, ResponseState to)
|
||||
|
|
|
@ -162,10 +162,10 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
|
||||
public void send(HttpExchange exchange)
|
||||
{
|
||||
Request request = exchange.getRequest();
|
||||
if (!queuedToBegin(request))
|
||||
if (!queuedToBegin(exchange))
|
||||
return;
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
ContentProvider contentProvider = request.getContent();
|
||||
HttpContent content = this.content = new HttpContent(contentProvider);
|
||||
|
||||
|
@ -198,7 +198,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
if (contentProvider instanceof AsyncContentProvider)
|
||||
((AsyncContentProvider)contentProvider).setListener(this);
|
||||
|
||||
if (!beginToHeaders(request))
|
||||
if (!beginToHeaders(exchange))
|
||||
return;
|
||||
|
||||
sendHeaders(exchange, content, commitCallback);
|
||||
|
@ -209,46 +209,61 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
return request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
}
|
||||
|
||||
protected boolean queuedToBegin(Request request)
|
||||
protected boolean queuedToBegin(HttpExchange exchange)
|
||||
{
|
||||
if (!updateRequestState(RequestState.QUEUED, RequestState.TRANSIENT))
|
||||
return false;
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request begin {}", request);
|
||||
RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
|
||||
notifier.notifyBegin(request);
|
||||
if (!updateRequestState(RequestState.TRANSIENT, RequestState.BEGIN))
|
||||
terminateRequest(getHttpExchange(), failure);
|
||||
|
||||
if (updateRequestState(RequestState.TRANSIENT, RequestState.BEGIN))
|
||||
return true;
|
||||
|
||||
terminateRequest(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean beginToHeaders(Request request)
|
||||
protected boolean beginToHeaders(HttpExchange exchange)
|
||||
{
|
||||
if (!updateRequestState(RequestState.BEGIN, RequestState.TRANSIENT))
|
||||
return false;
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request headers {}{}{}", request, System.lineSeparator(), request.getHeaders().toString().trim());
|
||||
RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
|
||||
notifier.notifyHeaders(request);
|
||||
if (!updateRequestState(RequestState.TRANSIENT, RequestState.HEADERS))
|
||||
terminateRequest(getHttpExchange(), failure);
|
||||
|
||||
if (updateRequestState(RequestState.TRANSIENT, RequestState.HEADERS))
|
||||
return true;
|
||||
|
||||
terminateRequest(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean headersToCommit(Request request)
|
||||
protected boolean headersToCommit(HttpExchange exchange)
|
||||
{
|
||||
if (!updateRequestState(RequestState.HEADERS, RequestState.TRANSIENT))
|
||||
return false;
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request committed {}", request);
|
||||
RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
|
||||
notifier.notifyCommit(request);
|
||||
if (!updateRequestState(RequestState.TRANSIENT, RequestState.COMMIT))
|
||||
terminateRequest(getHttpExchange(), failure);
|
||||
|
||||
if (updateRequestState(RequestState.TRANSIENT, RequestState.COMMIT))
|
||||
return true;
|
||||
|
||||
terminateRequest(exchange);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean someToContent(Request request, ByteBuffer content)
|
||||
protected boolean someToContent(HttpExchange exchange, ByteBuffer content)
|
||||
{
|
||||
RequestState current = requestState.get();
|
||||
switch (current)
|
||||
|
@ -256,15 +271,20 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
case COMMIT:
|
||||
case CONTENT:
|
||||
{
|
||||
if (!updateRequestState(current, RequestState.TRANSIENT_CONTENT))
|
||||
if (!updateRequestState(current, RequestState.TRANSIENT))
|
||||
return false;
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request content {}{}{}", request, System.lineSeparator(), BufferUtil.toDetailString(content));
|
||||
RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
|
||||
notifier.notifyContent(request, content);
|
||||
if (!updateRequestState(RequestState.TRANSIENT_CONTENT, RequestState.CONTENT))
|
||||
terminateRequest(getHttpExchange(), failure);
|
||||
|
||||
if (updateRequestState(RequestState.TRANSIENT, RequestState.CONTENT))
|
||||
return true;
|
||||
|
||||
terminateRequest(exchange);
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
@ -283,7 +303,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
{
|
||||
// Mark atomically the request as completed, with respect
|
||||
// to concurrency between request success and request failure.
|
||||
boolean completed = exchange.requestComplete();
|
||||
boolean completed = exchange.requestComplete(null);
|
||||
if (!completed)
|
||||
return false;
|
||||
|
||||
|
@ -292,18 +312,16 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
// Reset to be ready for another request.
|
||||
reset();
|
||||
|
||||
// Mark atomically the request as terminated and succeeded,
|
||||
// with respect to concurrency between request and response.
|
||||
Result result = exchange.terminateRequest(null);
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request success {}", request);
|
||||
HttpDestination destination = getHttpChannel().getHttpDestination();
|
||||
destination.getRequestNotifier().notifySuccess(exchange.getRequest());
|
||||
|
||||
// Mark atomically the request as terminated, with
|
||||
// respect to concurrency between request and response.
|
||||
Result result = exchange.terminateRequest();
|
||||
terminateRequest(exchange, null, result);
|
||||
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
|
@ -321,65 +339,22 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
|
||||
// Mark atomically the request as completed, with respect
|
||||
// to concurrency between request success and request failure.
|
||||
boolean completed = exchange.requestComplete();
|
||||
if (!completed)
|
||||
if (exchange.requestComplete(failure))
|
||||
return abort(exchange, failure);
|
||||
|
||||
return false;
|
||||
|
||||
this.failure = failure;
|
||||
|
||||
// Update the state to avoid more request processing.
|
||||
RequestState current;
|
||||
boolean fail;
|
||||
while (true)
|
||||
{
|
||||
current = requestState.get();
|
||||
if (updateRequestState(current, RequestState.FAILURE))
|
||||
{
|
||||
fail = current != RequestState.TRANSIENT && current != RequestState.TRANSIENT_CONTENT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dispose();
|
||||
|
||||
Result result = failRequest(exchange, failure);
|
||||
|
||||
if (fail)
|
||||
private void terminateRequest(HttpExchange exchange)
|
||||
{
|
||||
// In abort(), the state is updated before the failure is recorded
|
||||
// to avoid to overwrite it, so here we may read a null failure.
|
||||
Throwable failure = this.failure;
|
||||
if (failure == null)
|
||||
failure = new HttpRequestException("Concurrent failure", exchange.getRequest());
|
||||
Result result = exchange.terminateRequest();
|
||||
terminateRequest(exchange, failure, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Concurrent failure: request termination skipped, performed by helpers");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Result failRequest(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
// Mark atomically the request as terminated and failed,
|
||||
// with respect to concurrency between request and response.
|
||||
Result result = exchange.terminateRequest(failure);
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request failure {} {} on {}: {}", request, exchange, getHttpChannel(), failure);
|
||||
HttpDestination destination = getHttpChannel().getHttpDestination();
|
||||
destination.getRequestNotifier().notifyFailure(request, failure);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void terminateRequest(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
if (exchange != null)
|
||||
{
|
||||
Result result = failRequest(exchange, failure);
|
||||
terminateRequest(exchange, failure, result);
|
||||
}
|
||||
}
|
||||
|
||||
private void terminateRequest(HttpExchange exchange, Throwable failure, Result result)
|
||||
{
|
||||
|
@ -391,10 +366,13 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
if (result == null)
|
||||
{
|
||||
if (failure != null)
|
||||
{
|
||||
if (exchange.responseComplete(failure))
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Response failure from request {} {}", request, exchange);
|
||||
getHttpChannel().abortResponse(failure);
|
||||
getHttpChannel().abortResponse(exchange, failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -402,13 +380,13 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
HttpDestination destination = getHttpChannel().getHttpDestination();
|
||||
boolean ordered = destination.getHttpClient().isStrictEventOrdering();
|
||||
if (!ordered)
|
||||
channel.exchangeTerminated(result);
|
||||
channel.exchangeTerminated(exchange, result);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request/Response {}: {}", failure == null ? "succeeded" : "failed", result);
|
||||
HttpConversation conversation = exchange.getConversation();
|
||||
destination.getResponseNotifier().notifyComplete(conversation.getResponseListeners(), result);
|
||||
if (ordered)
|
||||
channel.exchangeTerminated(result);
|
||||
channel.exchangeTerminated(exchange, result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,9 +506,55 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
}
|
||||
}
|
||||
|
||||
public boolean abort(Throwable failure)
|
||||
public boolean abort(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
return anyToFailure(failure);
|
||||
// Update the state to avoid more request processing.
|
||||
boolean terminate;
|
||||
out: while (true)
|
||||
{
|
||||
RequestState current = requestState.get();
|
||||
switch (current)
|
||||
{
|
||||
case FAILURE:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (updateRequestState(current, RequestState.FAILURE))
|
||||
{
|
||||
terminate = current != RequestState.TRANSIENT;
|
||||
break out;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.failure = failure;
|
||||
|
||||
dispose();
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Request failure {} {} on {}: {}", request, exchange, getHttpChannel(), failure);
|
||||
HttpDestination destination = getHttpChannel().getHttpDestination();
|
||||
destination.getRequestNotifier().notifyFailure(request, failure);
|
||||
|
||||
if (terminate)
|
||||
{
|
||||
// Mark atomically the request as terminated, with
|
||||
// respect to concurrency between request and response.
|
||||
Result result = exchange.terminateRequest();
|
||||
terminateRequest(exchange, failure, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Concurrent failure: request termination skipped, performed by helpers");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean updateRequestState(RequestState from, RequestState to)
|
||||
|
@ -574,10 +598,6 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
* One of the state transition methods is being executed.
|
||||
*/
|
||||
TRANSIENT,
|
||||
/**
|
||||
* The content transition method is being executed.
|
||||
*/
|
||||
TRANSIENT_CONTENT,
|
||||
/**
|
||||
* The request is queued, the initial state
|
||||
*/
|
||||
|
@ -686,8 +706,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
if (exchange == null)
|
||||
return;
|
||||
|
||||
Request request = exchange.getRequest();
|
||||
if (!headersToCommit(request))
|
||||
if (!headersToCommit(exchange))
|
||||
return;
|
||||
|
||||
HttpContent content = HttpSender.this.content;
|
||||
|
@ -705,7 +724,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
ByteBuffer contentBuffer = content.getContent();
|
||||
if (contentBuffer != null)
|
||||
{
|
||||
if (!someToContent(request, contentBuffer))
|
||||
if (!someToContent(exchange, contentBuffer))
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -840,7 +859,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
|||
return;
|
||||
content.succeeded();
|
||||
ByteBuffer buffer = content.getContent();
|
||||
someToContent(exchange.getRequest(), buffer);
|
||||
someToContent(exchange, buffer);
|
||||
super.succeeded();
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ package org.eclipse.jetty.client.http;
|
|||
|
||||
import org.eclipse.jetty.client.HttpChannel;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.HttpReceiver;
|
||||
import org.eclipse.jetty.client.HttpSender;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
|
@ -51,6 +53,18 @@ public class HttpChannelOverHTTP extends HttpChannel
|
|||
return new HttpReceiverOverHTTP(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpSender getHttpSender()
|
||||
{
|
||||
return sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpReceiver getHttpReceiver()
|
||||
{
|
||||
return receiver;
|
||||
}
|
||||
|
||||
public HttpConnectionOverHTTP getHttpConnection()
|
||||
{
|
||||
return connection;
|
||||
|
@ -65,23 +79,9 @@ public class HttpChannelOverHTTP extends HttpChannel
|
|||
}
|
||||
|
||||
@Override
|
||||
public void proceed(HttpExchange exchange, Throwable failure)
|
||||
public void release()
|
||||
{
|
||||
sender.proceed(exchange, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
boolean sendAborted = sender.abort(cause);
|
||||
boolean receiveAborted = abortResponse(cause);
|
||||
return sendAborted || receiveAborted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abortResponse(Throwable cause)
|
||||
{
|
||||
return receiver.abort(cause);
|
||||
connection.release();
|
||||
}
|
||||
|
||||
public void receive()
|
||||
|
@ -90,9 +90,9 @@ public class HttpChannelOverHTTP extends HttpChannel
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exchangeTerminated(Result result)
|
||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||
{
|
||||
super.exchangeTerminated(result);
|
||||
super.exchangeTerminated(exchange, result);
|
||||
|
||||
Response response = result.getResponse();
|
||||
HttpFields responseHeaders = response.getHeaders();
|
||||
|
@ -115,7 +115,7 @@ public class HttpChannelOverHTTP extends HttpChannel
|
|||
if (close)
|
||||
connection.close();
|
||||
else
|
||||
connection.release();
|
||||
release();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -200,8 +200,10 @@ public class HttpConnectionOverHTTP extends AbstractConnection implements Connec
|
|||
endPoint.setIdleTimeout(request.getIdleTimeout());
|
||||
|
||||
// One channel per connection, just delegate the send
|
||||
channel.associate(exchange);
|
||||
if (channel.associate(exchange))
|
||||
channel.send();
|
||||
else
|
||||
channel.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,7 +23,7 @@ import java.io.OutputStream;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
@ -176,4 +176,84 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
|
|||
});
|
||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbortOnContentBeforeRequestTermination() throws Exception
|
||||
{
|
||||
start(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
OutputStream output = response.getOutputStream();
|
||||
output.write(1);
|
||||
output.flush();
|
||||
output.write(2);
|
||||
output.flush();
|
||||
}
|
||||
catch (IOException ignored)
|
||||
{
|
||||
// The client may have already closed, and we'll get an exception here, but it's expected
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
final CountDownLatch abortLatch = new CountDownLatch(1);
|
||||
final AtomicInteger completes = new AtomicInteger();
|
||||
final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(scheme)
|
||||
.onRequestSuccess(new org.eclipse.jetty.client.api.Request.SuccessListener()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(org.eclipse.jetty.client.api.Request request)
|
||||
{
|
||||
try
|
||||
{
|
||||
abortLatch.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
x.printStackTrace();
|
||||
}
|
||||
}
|
||||
})
|
||||
.onResponseContent(new Response.ContentListener()
|
||||
{
|
||||
@Override
|
||||
public void onContent(Response response, ByteBuffer content)
|
||||
{
|
||||
try
|
||||
{
|
||||
response.abort(new Exception());
|
||||
abortLatch.countDown();
|
||||
// Delay to let the request side to finish its processing.
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
x.printStackTrace();
|
||||
}
|
||||
}
|
||||
})
|
||||
.send(new Response.CompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
completes.incrementAndGet();
|
||||
Assert.assertTrue(result.isFailed());
|
||||
completeLatch.countDown();
|
||||
}
|
||||
});
|
||||
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
// Wait to be sure that the complete event is only notified once.
|
||||
Thread.sleep(1000);
|
||||
|
||||
Assert.assertEquals(1, completes.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,9 +74,10 @@ public class HttpReceiverOverHTTPTest
|
|||
HttpRequest request = (HttpRequest)client.newRequest("http://localhost");
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
HttpExchange exchange = new HttpExchange(destination, request, Collections.<Response.ResponseListener>singletonList(listener));
|
||||
connection.getHttpChannel().associate(exchange);
|
||||
exchange.requestComplete();
|
||||
exchange.terminateRequest(null);
|
||||
boolean associated = connection.getHttpChannel().associate(exchange);
|
||||
Assert.assertTrue(associated);
|
||||
exchange.requestComplete(null);
|
||||
exchange.terminateRequest();
|
||||
return exchange;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ import java.util.concurrent.TimeoutException;
|
|||
|
||||
import org.eclipse.jetty.client.HttpChannel;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.HttpReceiver;
|
||||
import org.eclipse.jetty.client.HttpSender;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||
|
@ -58,6 +60,18 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpSender getHttpSender()
|
||||
{
|
||||
return sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpReceiver getHttpReceiver()
|
||||
{
|
||||
return receiver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send()
|
||||
{
|
||||
|
@ -70,23 +84,9 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
}
|
||||
|
||||
@Override
|
||||
public void proceed(HttpExchange exchange, Throwable failure)
|
||||
public void release()
|
||||
{
|
||||
sender.proceed(exchange, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
boolean sendAborted = sender.abort(cause);
|
||||
boolean receiveAborted = abortResponse(cause);
|
||||
return sendAborted || receiveAborted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abortResponse(Throwable cause)
|
||||
{
|
||||
return receiver.abort(cause);
|
||||
connection.release(this);
|
||||
}
|
||||
|
||||
protected boolean responseBegin(int code, String reason)
|
||||
|
@ -132,15 +132,15 @@ public class HttpChannelOverFCGI extends HttpChannel
|
|||
}
|
||||
|
||||
@Override
|
||||
public void exchangeTerminated(Result result)
|
||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||
{
|
||||
super.exchangeTerminated(result);
|
||||
super.exchangeTerminated(exchange, result);
|
||||
idle.onClose();
|
||||
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||
if (result.isFailed())
|
||||
connection.close(result.getFailure());
|
||||
else if (!connection.closeByHTTP(responseHeaders))
|
||||
connection.release(this);
|
||||
release();
|
||||
}
|
||||
|
||||
protected void flush(Generator.Result... results)
|
||||
|
|
|
@ -286,8 +286,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
|||
int id = acquireRequest();
|
||||
HttpChannelOverFCGI channel = new HttpChannelOverFCGI(HttpConnectionOverFCGI.this, flusher, id, request.getIdleTimeout());
|
||||
channels.put(id, channel);
|
||||
channel.associate(exchange);
|
||||
if (channel.associate(exchange))
|
||||
channel.send();
|
||||
else
|
||||
channel.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -64,29 +64,15 @@ public class HttpChannelOverSPDY extends HttpChannel
|
|||
}
|
||||
|
||||
@Override
|
||||
public void proceed(HttpExchange exchange, Throwable failure)
|
||||
public void release()
|
||||
{
|
||||
sender.proceed(exchange, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
boolean sendAborted = sender.abort(cause);
|
||||
boolean receiveAborted = abortResponse(cause);
|
||||
return sendAborted || receiveAborted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abortResponse(Throwable cause)
|
||||
{
|
||||
return receiver.abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exchangeTerminated(Result result)
|
||||
{
|
||||
super.exchangeTerminated(result);
|
||||
connection.release(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||
{
|
||||
super.exchangeTerminated(exchange, result);
|
||||
release();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,8 +48,10 @@ public class HttpConnectionOverSPDY extends HttpConnection
|
|||
// One connection maps to N channels, so for each exchange we create a new channel
|
||||
HttpChannel channel = new HttpChannelOverSPDY(getHttpDestination(), this, session);
|
||||
channels.add(channel);
|
||||
channel.associate(exchange);
|
||||
if (channel.associate(exchange))
|
||||
channel.send();
|
||||
else
|
||||
channel.release();
|
||||
}
|
||||
|
||||
protected void release(HttpChannel channel)
|
||||
|
|
Loading…
Reference in New Issue