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:
parent
74b86bad35
commit
777ed6ad64
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 boolean isNonBlocking()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue