Fixes #1891 - Make HTTP/2 async error notifications configurable.

Introduced HttpConfiguration.notifyRemoteAsyncErrors, true by default.
This commit is contained in:
Simone Bordet 2017-10-12 12:13:02 +02:00
parent c454ce88e8
commit 4236f14955
3 changed files with 82 additions and 1 deletions

View File

@ -23,11 +23,14 @@ import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -35,11 +38,13 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
@ -181,6 +186,63 @@ public class AsyncServletTest extends AbstractTest
Assert.assertTrue(clientLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void testStartAsyncThenClientResetWithoutRemoteErrorNotification() throws Exception
{
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setNotifyRemoteAsyncErrors(false);
prepareServer(new HTTP2ServerConnectionFactory(httpConfiguration));
ServletContextHandler context = new ServletContextHandler(server, "/");
AtomicReference<AsyncContext> asyncContextRef = new AtomicReference<>();
CountDownLatch latch = new CountDownLatch(1);
context.addServlet(new ServletHolder(new HttpServlet()
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(0);
asyncContextRef.set(asyncContext);
latch.countDown();
}
}), servletPath + "/*");
server.start();
prepareClient();
client.start();
Session session = newClient(new Session.Listener.Adapter());
HttpFields fields = new HttpFields();
MetaData.Request metaData = newRequest("GET", fields);
HeadersFrame frame = new HeadersFrame(metaData, null, true);
FuturePromise<Stream> promise = new FuturePromise<>();
session.newStream(frame, promise, new Stream.Listener.Adapter());
Stream stream = promise.get(5, TimeUnit.SECONDS);
// Wait for the server to be in ASYNC_WAIT.
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
sleep(500);
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
// Wait for the reset to be processed by the server.
sleep(500);
AsyncContext asyncContext = asyncContextRef.get();
ServletResponse response = asyncContext.getResponse();
ServletOutputStream output = response.getOutputStream();
try
{
// Large writes or explicit flush() must
// fail because the stream has been reset.
output.flush();
Assert.fail();
}
catch (IOException x)
{
// Expected
}
}
@Test
public void testStartAsyncThenServerSessionIdleTimeout() throws Exception
{

View File

@ -409,7 +409,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
{
if (handle)
handleWithContext();
else
else if (getHttpConfiguration().isNotifyRemoteAsyncErrors())
getState().asyncError(failure);
callback.succeeded();
}

View File

@ -67,6 +67,7 @@ public class HttpConfiguration
private int _maxErrorDispatches = 10;
private long _minRequestDataRate;
private CookieCompliance _cookieCompliance = CookieCompliance.RFC6265;
private boolean _notifyRemoteAsyncErrors = true;
/**
* <p>An interface that allows a request object to be customized
@ -127,6 +128,7 @@ public class HttpConfiguration
_maxErrorDispatches=config._maxErrorDispatches;
_minRequestDataRate=config._minRequestDataRate;
_cookieCompliance=config._cookieCompliance;
_notifyRemoteAsyncErrors=config._notifyRemoteAsyncErrors;
}
/**
@ -508,6 +510,23 @@ public class HttpConfiguration
return _cookieCompliance.equals(compliance);
}
/**
* @param notifyRemoteAsyncErrors whether remote errors, when detected, are notified to async applications
*/
public void setNotifyRemoteAsyncErrors(boolean notifyRemoteAsyncErrors)
{
this._notifyRemoteAsyncErrors = notifyRemoteAsyncErrors;
}
/**
* @return whether remote errors, when detected, are notified to async applications
*/
@ManagedAttribute("Whether remote errors, when detected, are notified to async applications")
public boolean isNotifyRemoteAsyncErrors()
{
return _notifyRemoteAsyncErrors;
}
@Override
public String toString()
{