Merged branch 'jetty-9.3.x' into 'jetty-9.4.x'.
This commit is contained in:
commit
716c595cdf
|
@ -64,6 +64,10 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
return new ContinueListener();
|
||||
}
|
||||
|
||||
protected void onContinue(Request request)
|
||||
{
|
||||
}
|
||||
|
||||
protected class ContinueListener extends BufferingResponseListener
|
||||
{
|
||||
@Override
|
||||
|
@ -72,7 +76,8 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
// Handling of success must be done here and not from onComplete(),
|
||||
// since the onComplete() is not invoked because the request is not completed yet.
|
||||
|
||||
HttpConversation conversation = ((HttpRequest)response.getRequest()).getConversation();
|
||||
Request request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
// Mark the 100 Continue response as handled
|
||||
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
|
||||
|
||||
|
@ -88,6 +93,7 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
// All good, continue
|
||||
exchange.resetResponse();
|
||||
exchange.proceed(null);
|
||||
onContinue(request);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -98,7 +104,7 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
|
||||
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
|
||||
notifier.forwardSuccess(listeners, contentResponse);
|
||||
exchange.proceed(new HttpRequestException("Expectation failed", exchange.getRequest()));
|
||||
exchange.proceed(new HttpRequestException("Expectation failed", request));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -68,21 +67,23 @@ public abstract class HttpConnection implements Connection
|
|||
@Override
|
||||
public void send(Request request, Response.CompleteListener listener)
|
||||
{
|
||||
ArrayList<Response.ResponseListener> listeners = new ArrayList<>(2);
|
||||
if (request.getTimeout() > 0)
|
||||
HttpRequest httpRequest = (HttpRequest)request;
|
||||
|
||||
ArrayList<Response.ResponseListener> listeners = new ArrayList<>(httpRequest.getResponseListeners());
|
||||
if (httpRequest.getTimeout() > 0)
|
||||
{
|
||||
TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(request);
|
||||
TimeoutCompleteListener timeoutListener = new TimeoutCompleteListener(httpRequest);
|
||||
timeoutListener.schedule(getHttpClient().getScheduler());
|
||||
listeners.add(timeoutListener);
|
||||
}
|
||||
if (listener != null)
|
||||
listeners.add(listener);
|
||||
|
||||
HttpExchange exchange = new HttpExchange(getHttpDestination(), (HttpRequest)request, listeners);
|
||||
HttpExchange exchange = new HttpExchange(getHttpDestination(), httpRequest, listeners);
|
||||
|
||||
SendFailure result = send(exchange);
|
||||
if (result != null)
|
||||
request.abort(result.failure);
|
||||
httpRequest.abort(result.failure);
|
||||
}
|
||||
|
||||
protected abstract SendFailure send(HttpExchange exchange);
|
||||
|
|
|
@ -693,6 +693,11 @@ public class HttpRequest implements Request
|
|||
client.send(request, responseListeners);
|
||||
}
|
||||
|
||||
protected List<Response.ResponseListener> getResponseListeners()
|
||||
{
|
||||
return responseListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean abort(Throwable cause)
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.api.Connection;
|
||||
|
@ -27,7 +28,7 @@ import org.eclipse.jetty.client.api.Request;
|
|||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.Assert;
|
||||
|
@ -65,7 +66,6 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
}
|
||||
}
|
||||
|
||||
@Slow
|
||||
@Test
|
||||
public void testExplicitConnectionIsClosedOnRemoteClose() throws Exception
|
||||
{
|
||||
|
@ -98,4 +98,26 @@ public class HttpClientExplicitConnectionTest extends AbstractHttpClientServerTe
|
|||
Assert.assertTrue(connectionPool.getActiveConnections().isEmpty());
|
||||
Assert.assertTrue(connectionPool.getIdleConnections().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitConnectionResponseListeners() throws Exception
|
||||
{
|
||||
start(new EmptyServerHandler());
|
||||
|
||||
Destination destination = client.getDestination(scheme, "localhost", connector.getLocalPort());
|
||||
FuturePromise<Connection> futureConnection = new FuturePromise<>();
|
||||
destination.newConnection(futureConnection);
|
||||
Connection connection = futureConnection.get(5, TimeUnit.SECONDS);
|
||||
CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
Request request = client.newRequest(destination.getHost(), destination.getPort())
|
||||
.scheme(scheme)
|
||||
.onResponseSuccess(response -> responseLatch.countDown());
|
||||
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
connection.send(request, listener);
|
||||
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -997,8 +997,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
|
||||
protected void abort(Throwable failure)
|
||||
{
|
||||
terminate();
|
||||
notifyFailure(this, failure);
|
||||
terminate();
|
||||
}
|
||||
|
||||
public boolean isDisconnected()
|
||||
|
|
|
@ -162,8 +162,14 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
{
|
||||
dataInfo = queue.poll();
|
||||
}
|
||||
|
||||
if (dataInfo == null)
|
||||
{
|
||||
DataInfo prevDataInfo = this.dataInfo;
|
||||
if (prevDataInfo != null && prevDataInfo.last)
|
||||
return Action.SUCCEEDED;
|
||||
return Action.IDLE;
|
||||
}
|
||||
|
||||
this.dataInfo = dataInfo;
|
||||
responseContent(dataInfo.exchange, dataInfo.buffer, this);
|
||||
|
@ -176,11 +182,15 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
|||
ByteBufferPool byteBufferPool = getHttpDestination().getHttpClient().getByteBufferPool();
|
||||
byteBufferPool.release(dataInfo.buffer);
|
||||
dataInfo.callback.succeeded();
|
||||
if (dataInfo.last)
|
||||
responseSuccess(dataInfo.exchange);
|
||||
super.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
responseSuccess(dataInfo.exchange);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable failure)
|
||||
{
|
||||
|
|
|
@ -133,6 +133,12 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
getConnection().onSessionFailure(new IOException("HTTP/2 " + error + reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Session session, Throwable failure)
|
||||
{
|
||||
getConnection().onSessionFailure(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
|
|
|
@ -44,7 +44,6 @@ import org.eclipse.jetty.util.BufferUtil;
|
|||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
|
||||
|
||||
public class HttpChannelOverHTTP2 extends HttpChannel
|
||||
{
|
||||
|
@ -297,8 +296,10 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
|||
|
||||
public void onFailure(Throwable failure)
|
||||
{
|
||||
onEarlyEOF();
|
||||
getState().asyncError(failure);
|
||||
if (onEarlyEOF())
|
||||
handle();
|
||||
else
|
||||
getState().asyncError(failure);
|
||||
}
|
||||
|
||||
protected void consumeInput()
|
||||
|
|
|
@ -40,7 +40,9 @@ import javax.servlet.http.HttpServlet;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.ContinueProtocolHandler;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.ProtocolHandlers;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
@ -80,6 +82,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|||
*/
|
||||
public abstract class AbstractProxyServlet extends HttpServlet
|
||||
{
|
||||
protected static final String CLIENT_REQUEST_ATTRIBUTE = "org.eclipse.jetty.proxy.clientRequest";
|
||||
protected static final Set<String> HOP_HEADERS;
|
||||
static
|
||||
{
|
||||
|
@ -324,8 +327,10 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
// Content must not be decoded, otherwise the client gets confused.
|
||||
client.getContentDecoderFactories().clear();
|
||||
|
||||
// No protocol handlers, pass everything to the client.
|
||||
client.getProtocolHandlers().clear();
|
||||
// Pass traffic to the client, only intercept what's necessary.
|
||||
ProtocolHandlers protocolHandlers = client.getProtocolHandlers();
|
||||
protocolHandlers.clear();
|
||||
protocolHandlers.put(new ProxyContinueProtocolHandler());
|
||||
|
||||
return client;
|
||||
}
|
||||
|
@ -427,6 +432,11 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
clientRequest.getHeader(HttpHeader.TRANSFER_ENCODING.asString()) != null;
|
||||
}
|
||||
|
||||
protected boolean expects100Continue(HttpServletRequest request)
|
||||
{
|
||||
return HttpHeaderValue.CONTINUE.asString().equals(request.getHeader(HttpHeader.EXPECT.asString()));
|
||||
}
|
||||
|
||||
protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
// First clear possibly existing headers, as we are going to copy those from the client request.
|
||||
|
@ -638,6 +648,9 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
int status = failure instanceof TimeoutException ?
|
||||
HttpStatus.GATEWAY_TIMEOUT_504 :
|
||||
HttpStatus.BAD_GATEWAY_502;
|
||||
int serverStatus = serverResponse == null ? status : serverResponse.getStatus();
|
||||
if (expects100Continue(clientRequest) && serverStatus >= HttpStatus.OK_200)
|
||||
status = serverStatus;
|
||||
sendProxyResponseError(clientRequest, proxyResponse, status);
|
||||
}
|
||||
}
|
||||
|
@ -655,6 +668,12 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
clientRequest.getAsyncContext().complete();
|
||||
}
|
||||
|
||||
protected void onContinue(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
if (_log.isDebugEnabled())
|
||||
_log.debug("{} handling 100 Continue", getRequestId(clientRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Utility class that implement transparent proxy functionalities.</p>
|
||||
* <p>Configuration parameters:</p>
|
||||
|
@ -733,4 +752,14 @@ public abstract class AbstractProxyServlet extends HttpServlet
|
|||
return rewrittenURI.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class ProxyContinueProtocolHandler extends ContinueProtocolHandler
|
||||
{
|
||||
@Override
|
||||
protected void onContinue(Request request)
|
||||
{
|
||||
HttpServletRequest clientRequest = (HttpServletRequest)request.getAttributes().get(CLIENT_REQUEST_ATTRIBUTE);
|
||||
AbstractProxyServlet.this.onContinue(clientRequest, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.client.ContentDecoder;
|
||||
import org.eclipse.jetty.client.GZIPContentDecoder;
|
||||
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;
|
||||
|
@ -66,9 +65,10 @@ import org.eclipse.jetty.util.component.Destroyable;
|
|||
*/
|
||||
public class AsyncMiddleManServlet extends AbstractProxyServlet
|
||||
{
|
||||
private static final String PROXY_REQUEST_COMMITTED = AsyncMiddleManServlet.class.getName() + ".proxyRequestCommitted";
|
||||
private static final String CLIENT_TRANSFORMER = AsyncMiddleManServlet.class.getName() + ".clientTransformer";
|
||||
private static final String SERVER_TRANSFORMER = AsyncMiddleManServlet.class.getName() + ".serverTransformer";
|
||||
private static final String PROXY_REQUEST_CONTENT_COMMITTED_ATTRIBUTE = AsyncMiddleManServlet.class.getName() + ".proxyRequestContentCommitted";
|
||||
private static final String CLIENT_TRANSFORMER_ATTRIBUTE = AsyncMiddleManServlet.class.getName() + ".clientTransformer";
|
||||
private static final String SERVER_TRANSFORMER_ATTRIBUTE = AsyncMiddleManServlet.class.getName() + ".serverTransformer";
|
||||
private static final String CONTINUE_ACTION_ATTRIBUTE = AsyncMiddleManServlet.class.getName() + ".continueAction";
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest clientRequest, HttpServletResponse proxyResponse) throws ServletException, IOException
|
||||
|
@ -91,8 +91,6 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
.method(clientRequest.getMethod())
|
||||
.version(HttpVersion.fromString(clientRequest.getProtocol()));
|
||||
|
||||
boolean hasContent = hasContent(clientRequest);
|
||||
|
||||
copyRequestHeaders(clientRequest, proxyRequest);
|
||||
|
||||
addProxyHeaders(clientRequest, proxyRequest);
|
||||
|
@ -105,16 +103,43 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
// If there is content, the send of the proxy request
|
||||
// is delayed and performed when the content arrives,
|
||||
// to allow optimization of the Content-Length header.
|
||||
if (hasContent)
|
||||
proxyRequest.content(newProxyContentProvider(clientRequest, proxyResponse, proxyRequest));
|
||||
if (hasContent(clientRequest))
|
||||
{
|
||||
DeferredContentProvider provider = newProxyContentProvider(clientRequest, proxyResponse, proxyRequest);
|
||||
proxyRequest.content(provider);
|
||||
|
||||
if (expects100Continue(clientRequest))
|
||||
{
|
||||
proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, clientRequest);
|
||||
proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
ServletInputStream input = clientRequest.getInputStream();
|
||||
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider));
|
||||
}
|
||||
catch (Throwable failure)
|
||||
{
|
||||
onClientRequestFailure(clientRequest, proxyRequest, proxyResponse, failure);
|
||||
}
|
||||
});
|
||||
sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServletInputStream input = clientRequest.getInputStream();
|
||||
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
|
||||
}
|
||||
}
|
||||
|
||||
protected ContentProvider newProxyContentProvider(final HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) throws IOException
|
||||
protected DeferredContentProvider newProxyContentProvider(final HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) throws IOException
|
||||
{
|
||||
ServletInputStream input = clientRequest.getInputStream();
|
||||
DeferredContentProvider provider = new DeferredContentProvider()
|
||||
return new DeferredContentProvider()
|
||||
{
|
||||
@Override
|
||||
public boolean offer(ByteBuffer buffer, Callback callback)
|
||||
|
@ -124,8 +149,6 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
return super.offer(buffer, callback);
|
||||
}
|
||||
};
|
||||
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider));
|
||||
return provider;
|
||||
}
|
||||
|
||||
protected ReadListener newProxyReadListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider)
|
||||
|
@ -154,6 +177,14 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
return ContentTransformer.IDENTITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onContinue(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
super.onContinue(clientRequest, proxyRequest);
|
||||
Runnable action = (Runnable)proxyRequest.getAttributes().get(CONTINUE_ACTION_ATTRIBUTE);
|
||||
action.run();
|
||||
}
|
||||
|
||||
private void transform(ContentTransformer transformer, ByteBuffer input, boolean finished, List<ByteBuffer> output) throws IOException
|
||||
{
|
||||
try
|
||||
|
@ -197,10 +228,10 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
|
||||
private void cleanup(HttpServletRequest clientRequest)
|
||||
{
|
||||
ContentTransformer clientTransformer = (ContentTransformer)clientRequest.getAttribute(CLIENT_TRANSFORMER);
|
||||
ContentTransformer clientTransformer = (ContentTransformer)clientRequest.getAttribute(CLIENT_TRANSFORMER_ATTRIBUTE);
|
||||
if (clientTransformer instanceof Destroyable)
|
||||
((Destroyable)clientTransformer).destroy();
|
||||
ContentTransformer serverTransformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER);
|
||||
ContentTransformer serverTransformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER_ATTRIBUTE);
|
||||
if (serverTransformer instanceof Destroyable)
|
||||
((Destroyable)serverTransformer).destroy();
|
||||
}
|
||||
|
@ -237,6 +268,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
private final Request proxyRequest;
|
||||
private final DeferredContentProvider provider;
|
||||
private final int contentLength;
|
||||
private final boolean expects100Continue;
|
||||
private int length;
|
||||
|
||||
protected ProxyReader(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider)
|
||||
|
@ -246,6 +278,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
this.proxyRequest = proxyRequest;
|
||||
this.provider = provider;
|
||||
this.contentLength = clientRequest.getContentLength();
|
||||
this.expects100Continue = expects100Continue(clientRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -321,15 +354,13 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
|
||||
private void process(ByteBuffer content, Callback callback, boolean finished) throws IOException
|
||||
{
|
||||
ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(CLIENT_TRANSFORMER);
|
||||
ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(CLIENT_TRANSFORMER_ATTRIBUTE);
|
||||
if (transformer == null)
|
||||
{
|
||||
transformer = newClientRequestContentTransformer(clientRequest, proxyRequest);
|
||||
clientRequest.setAttribute(CLIENT_TRANSFORMER, transformer);
|
||||
clientRequest.setAttribute(CLIENT_TRANSFORMER_ATTRIBUTE, transformer);
|
||||
}
|
||||
|
||||
boolean committed = clientRequest.getAttribute(PROXY_REQUEST_COMMITTED) != null;
|
||||
|
||||
int contentBytes = content.remaining();
|
||||
|
||||
// Skip transformation for empty non-last buffers.
|
||||
|
@ -361,11 +392,15 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
if (_log.isDebugEnabled())
|
||||
_log.debug("{} upstream content transformation {} -> {} bytes", getRequestId(clientRequest), contentBytes, newContentBytes);
|
||||
|
||||
if (!committed && (size > 0 || finished))
|
||||
boolean contentCommitted = clientRequest.getAttribute(PROXY_REQUEST_CONTENT_COMMITTED_ATTRIBUTE) != null;
|
||||
if (!contentCommitted && (size > 0 || finished))
|
||||
{
|
||||
proxyRequest.header(HttpHeader.CONTENT_LENGTH, null);
|
||||
clientRequest.setAttribute(PROXY_REQUEST_COMMITTED, true);
|
||||
sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
|
||||
clientRequest.setAttribute(PROXY_REQUEST_CONTENT_COMMITTED_ATTRIBUTE, true);
|
||||
if (!expects100Continue)
|
||||
{
|
||||
proxyRequest.header(HttpHeader.CONTENT_LENGTH, null);
|
||||
sendProxyRequest(clientRequest, proxyResponse, proxyRequest);
|
||||
}
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
|
@ -401,6 +436,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
@Override
|
||||
public void onBegin(Response serverResponse)
|
||||
{
|
||||
response = serverResponse;
|
||||
proxyResponse.setStatus(serverResponse.getStatus());
|
||||
}
|
||||
|
||||
|
@ -430,11 +466,11 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
clientRequest.setAttribute(WRITE_LISTENER_ATTRIBUTE, proxyWriter);
|
||||
}
|
||||
|
||||
ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER);
|
||||
ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER_ATTRIBUTE);
|
||||
if (transformer == null)
|
||||
{
|
||||
transformer = newServerResponseContentTransformer(clientRequest, proxyResponse, serverResponse);
|
||||
clientRequest.setAttribute(SERVER_TRANSFORMER, transformer);
|
||||
clientRequest.setAttribute(SERVER_TRANSFORMER_ATTRIBUTE, transformer);
|
||||
}
|
||||
|
||||
length += contentBytes;
|
||||
|
@ -502,7 +538,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
if (contentLength < 0)
|
||||
{
|
||||
ProxyWriter proxyWriter = (ProxyWriter)clientRequest.getAttribute(WRITE_LISTENER_ATTRIBUTE);
|
||||
ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER);
|
||||
ContentTransformer transformer = (ContentTransformer)clientRequest.getAttribute(SERVER_TRANSFORMER_ATTRIBUTE);
|
||||
|
||||
transform(transformer, BufferUtil.EMPTY_BUFFER, true, buffers);
|
||||
|
||||
|
@ -544,7 +580,6 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
|
|||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
response = result.getResponse();
|
||||
if (result.isSucceeded())
|
||||
complete.succeeded();
|
||||
else
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.proxy;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
|
@ -29,13 +32,16 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.AsyncContentProvider;
|
||||
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.client.util.DeferredContentProvider;
|
||||
import org.eclipse.jetty.client.util.InputStreamContentProvider;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IteratingCallback;
|
||||
|
||||
/**
|
||||
* <p>Servlet 3.0 asynchronous proxy servlet.</p>
|
||||
|
@ -47,6 +53,8 @@ import org.eclipse.jetty.util.Callback;
|
|||
*/
|
||||
public class ProxyServlet extends AbstractProxyServlet
|
||||
{
|
||||
private static final String CONTINUE_ACTION_ATTRIBUTE = ProxyServlet.class.getName() + ".continueAction";
|
||||
|
||||
@Override
|
||||
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
|
@ -83,7 +91,30 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS);
|
||||
|
||||
if (hasContent(request))
|
||||
proxyRequest.content(proxyRequestContent(request, response, proxyRequest));
|
||||
{
|
||||
if (expects100Continue(request))
|
||||
{
|
||||
DeferredContentProvider deferred = new DeferredContentProvider();
|
||||
proxyRequest.content(deferred);
|
||||
proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, request);
|
||||
proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
ContentProvider provider = proxyRequestContent(request, response, proxyRequest);
|
||||
new DelegatingContentProvider(request, proxyRequest, response, provider, deferred).iterate();
|
||||
}
|
||||
catch (Throwable failure)
|
||||
{
|
||||
onClientRequestFailure(request, proxyRequest, response, failure);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
proxyRequest.content(proxyRequestContent(request, response, proxyRequest));
|
||||
}
|
||||
}
|
||||
|
||||
sendProxyRequest(request, response, proxyRequest);
|
||||
}
|
||||
|
@ -114,6 +145,15 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onContinue(HttpServletRequest clientRequest, Request proxyRequest)
|
||||
{
|
||||
super.onContinue(clientRequest, proxyRequest);
|
||||
Runnable action = (Runnable)proxyRequest.getAttributes().get(CONTINUE_ACTION_ATTRIBUTE);
|
||||
Executor executor = getHttpClient().getExecutor();
|
||||
executor.execute(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Convenience extension of {@link ProxyServlet} that offers transparent proxy functionalities.</p>
|
||||
*
|
||||
|
@ -240,4 +280,81 @@ public class ProxyServlet extends AbstractProxyServlet
|
|||
onClientRequestFailure(request, proxyRequest, response, failure);
|
||||
}
|
||||
}
|
||||
|
||||
private class DelegatingContentProvider extends IteratingCallback implements AsyncContentProvider.Listener
|
||||
{
|
||||
private final HttpServletRequest clientRequest;
|
||||
private final Request proxyRequest;
|
||||
private final HttpServletResponse proxyResponse;
|
||||
private final Iterator<ByteBuffer> iterator;
|
||||
private final DeferredContentProvider deferred;
|
||||
|
||||
private DelegatingContentProvider(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, ContentProvider provider, DeferredContentProvider deferred)
|
||||
{
|
||||
this.clientRequest = clientRequest;
|
||||
this.proxyRequest = proxyRequest;
|
||||
this.proxyResponse = proxyResponse;
|
||||
this.iterator = provider.iterator();
|
||||
this.deferred = deferred;
|
||||
if (provider instanceof AsyncContentProvider)
|
||||
((AsyncContentProvider)provider).setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Action process() throws Exception
|
||||
{
|
||||
if (!iterator.hasNext())
|
||||
return Action.SUCCEEDED;
|
||||
|
||||
ByteBuffer buffer = iterator.next();
|
||||
if (buffer == null)
|
||||
return Action.IDLE;
|
||||
|
||||
deferred.offer(buffer, this);
|
||||
return Action.SCHEDULED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
if (iterator instanceof Callback)
|
||||
((Callback)iterator).succeeded();
|
||||
super.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (iterator instanceof Closeable)
|
||||
((Closeable)iterator).close();
|
||||
deferred.close();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
_log.ignore(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(Throwable failure)
|
||||
{
|
||||
if (iterator instanceof Callback)
|
||||
((Callback)iterator).failed(failure);
|
||||
onClientRequestFailure(clientRequest, proxyRequest, proxyResponse, failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContent()
|
||||
{
|
||||
iterate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ import javax.servlet.Filter;
|
|||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
@ -72,9 +73,12 @@ import org.eclipse.jetty.client.api.Result;
|
|||
import org.eclipse.jetty.client.http.HttpDestinationOverHTTP;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
||||
import org.eclipse.jetty.client.util.InputStreamResponseListener;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
|
@ -187,9 +191,12 @@ public class ProxyServletTest
|
|||
@After
|
||||
public void dispose() throws Exception
|
||||
{
|
||||
client.stop();
|
||||
proxy.stop();
|
||||
server.stop();
|
||||
if (client != null)
|
||||
client.stop();
|
||||
if (proxy != null)
|
||||
proxy.stop();
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1233,5 +1240,227 @@ public class ProxyServletTest
|
|||
Assert.assertEquals(200, response.getStatus());
|
||||
}
|
||||
|
||||
// TODO: test proxy authentication
|
||||
@Test
|
||||
public void testExpect100ContinueRespond100Continue() throws Exception
|
||||
{
|
||||
CountDownLatch serverLatch1 = new CountDownLatch(1);
|
||||
CountDownLatch serverLatch2 = new CountDownLatch(1);
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
serverLatch1.countDown();
|
||||
|
||||
try
|
||||
{
|
||||
serverLatch2.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
|
||||
// Echo the content.
|
||||
IO.copy(input, response.getOutputStream());
|
||||
}
|
||||
});
|
||||
startProxy();
|
||||
startClient();
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
CountDownLatch contentLatch = new CountDownLatch(1);
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
|
||||
.content(new BytesContentProvider(content))
|
||||
.onRequestContent((request, buffer) -> contentLatch.countDown())
|
||||
.send(new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
if (result.getResponse().getStatus() == HttpStatus.OK_200)
|
||||
{
|
||||
if (Arrays.equals(content, getContent()))
|
||||
clientLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait until we arrive on the server.
|
||||
Assert.assertTrue(serverLatch1.await(5, TimeUnit.SECONDS));
|
||||
// The client should not send the content yet.
|
||||
Assert.assertFalse(contentLatch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
// Make the server send the 100 Continue.
|
||||
serverLatch2.countDown();
|
||||
|
||||
// The client has sent the content.
|
||||
Assert.assertTrue(contentLatch.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpect100ContinueRespond100ContinueDelayedRequestContent() throws Exception
|
||||
{
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
// Echo the content.
|
||||
IO.copy(input, response.getOutputStream());
|
||||
}
|
||||
});
|
||||
startProxy();
|
||||
startClient();
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
new Random().nextBytes(content);
|
||||
int chunk1 = content.length / 2;
|
||||
DeferredContentProvider contentProvider = new DeferredContentProvider();
|
||||
contentProvider.offer(ByteBuffer.wrap(content, 0, chunk1));
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
|
||||
.content(contentProvider)
|
||||
.send(new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
if (result.getResponse().getStatus() == HttpStatus.OK_200)
|
||||
{
|
||||
if (Arrays.equals(content, getContent()))
|
||||
clientLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wait a while and then offer more content.
|
||||
Thread.sleep(1000);
|
||||
contentProvider.offer(ByteBuffer.wrap(content, chunk1, content.length - chunk1));
|
||||
contentProvider.close();
|
||||
|
||||
Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpect100ContinueRespond100ContinueSomeRequestContentThenFailure() throws Exception
|
||||
{
|
||||
CountDownLatch serverLatch = new CountDownLatch(1);
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
// Send the 100 Continue.
|
||||
ServletInputStream input = request.getInputStream();
|
||||
try
|
||||
{
|
||||
// Echo the content.
|
||||
IO.copy(input, response.getOutputStream());
|
||||
}
|
||||
catch (IOException x)
|
||||
{
|
||||
serverLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
startProxy();
|
||||
startClient();
|
||||
|
||||
long idleTimeout = 1000;
|
||||
client.setIdleTimeout(idleTimeout);
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
new Random().nextBytes(content);
|
||||
int chunk1 = content.length / 2;
|
||||
DeferredContentProvider contentProvider = new DeferredContentProvider();
|
||||
contentProvider.offer(ByteBuffer.wrap(content, 0, chunk1));
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
|
||||
.content(contentProvider)
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
clientLatch.countDown();
|
||||
});
|
||||
|
||||
// Wait more than the idle timeout to break the connection.
|
||||
Thread.sleep(2 * idleTimeout);
|
||||
|
||||
Assert.assertTrue(serverLatch.await(555, TimeUnit.SECONDS));
|
||||
Assert.assertTrue(clientLatch.await(555, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpect100ContinueRespond417ExpectationFailed() throws Exception
|
||||
{
|
||||
CountDownLatch serverLatch1 = new CountDownLatch(1);
|
||||
CountDownLatch serverLatch2 = new CountDownLatch(1);
|
||||
startServer(new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
serverLatch1.countDown();
|
||||
|
||||
try
|
||||
{
|
||||
serverLatch2.await(5, TimeUnit.SECONDS);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
|
||||
// Send the 417 Expectation Failed.
|
||||
response.setStatus(HttpStatus.EXPECTATION_FAILED_417);
|
||||
}
|
||||
});
|
||||
startProxy();
|
||||
startClient();
|
||||
|
||||
byte[] content = new byte[1024];
|
||||
CountDownLatch contentLatch = new CountDownLatch(1);
|
||||
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", serverConnector.getLocalPort())
|
||||
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
|
||||
.content(new BytesContentProvider(content))
|
||||
.onRequestContent((request, buffer) -> contentLatch.countDown())
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
{
|
||||
if (result.getResponse().getStatus() == HttpStatus.EXPECTATION_FAILED_417)
|
||||
clientLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait until we arrive on the server.
|
||||
Assert.assertTrue(serverLatch1.await(5, TimeUnit.SECONDS));
|
||||
// The client should not send the content yet.
|
||||
Assert.assertFalse(contentLatch.await(1, TimeUnit.SECONDS));
|
||||
|
||||
// Make the server send the 417 Expectation Failed.
|
||||
serverLatch2.countDown();
|
||||
|
||||
// The client should not send the content.
|
||||
Assert.assertFalse(contentLatch.await(1, TimeUnit.SECONDS));
|
||||
Assert.assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -222,8 +222,8 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
|||
// If we have no request yet, just close
|
||||
if (_metadata.getMethod() == null)
|
||||
_httpConnection.close();
|
||||
else
|
||||
onEarlyEOF();
|
||||
else if (onEarlyEOF())
|
||||
handle();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
|
|
@ -21,6 +21,8 @@ package org.eclipse.jetty.http.client;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpClientTransport;
|
||||
|
@ -40,6 +42,8 @@ import org.eclipse.jetty.server.SecureRequestCustomizer;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||
import org.eclipse.jetty.util.SocketAddressResolver;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
|
@ -67,6 +71,8 @@ public abstract class AbstractTest
|
|||
protected SslContextFactory sslContextFactory;
|
||||
protected Server server;
|
||||
protected ServerConnector connector;
|
||||
protected ServletContextHandler context;
|
||||
protected String servletPath = "/servlet";
|
||||
protected HttpClient client;
|
||||
|
||||
public AbstractTest(Transport transport)
|
||||
|
@ -81,6 +87,22 @@ public abstract class AbstractTest
|
|||
startClient();
|
||||
}
|
||||
|
||||
public void start(HttpServlet servlet) throws Exception
|
||||
{
|
||||
startServer(servlet);
|
||||
startClient();
|
||||
}
|
||||
|
||||
protected void startServer(HttpServlet servlet) throws Exception
|
||||
{
|
||||
context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
ServletHolder holder = new ServletHolder(servlet);
|
||||
holder.setAsyncSupported(true);
|
||||
context.addServlet(holder, servletPath);
|
||||
startServer(context);
|
||||
}
|
||||
|
||||
protected void startServer(Handler handler) throws Exception
|
||||
{
|
||||
sslContextFactory = new SslContextFactory();
|
||||
|
@ -228,9 +250,19 @@ public abstract class AbstractTest
|
|||
|
||||
@After
|
||||
public void stop() throws Exception
|
||||
{
|
||||
stopClient();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
protected void stopClient() throws Exception
|
||||
{
|
||||
if (client != null)
|
||||
client.stop();
|
||||
}
|
||||
|
||||
protected void stopServer() throws Exception
|
||||
{
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -488,6 +488,28 @@ public class HttpClientTest extends AbstractTest
|
|||
Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseWithContentCompleteListenerInvokedOnce() throws Exception
|
||||
{
|
||||
start(new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
super.handle(target, baseRequest, request, response);
|
||||
response.getWriter().write("Jetty");
|
||||
}
|
||||
});
|
||||
|
||||
AtomicInteger completes = new AtomicInteger();
|
||||
client.newRequest(newURI())
|
||||
.send(result -> completes.incrementAndGet());
|
||||
|
||||
sleep(1000);
|
||||
|
||||
Assert.assertEquals(1, completes.get());
|
||||
}
|
||||
|
||||
private void sleep(long time) throws IOException
|
||||
{
|
||||
try
|
||||
|
|
Loading…
Reference in New Issue