Fixes #277 - Proxy servlet does not handle HTTP status 100 correctly.

Introduced overridable ContinueProtocolHandler.onContinue(), and
making sure that proxy servlets use a ContinueProtocolHandler
subclass to intercept 100 Continue responses from the server, so that
they can relay it properly to the client.
This commit is contained in:
Simone Bordet 2016-09-26 12:05:03 +02:00
parent 74b86bad35
commit 777ed6ad64
5 changed files with 452 additions and 36 deletions

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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 boolean isNonBlocking()
{
return true;
}
@Override
public void onContent()
{
iterate();
}
}
}

View File

@ -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;
@ -71,9 +72,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;
@ -186,9 +190,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
@ -1230,5 +1237,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));
}
}