Fixes #1891 - Make HTTP/2 async error notifications configurable.
Introduced HttpConfiguration.notifyRemoteAsyncErrors, true by default.
This commit is contained in:
parent
c454ce88e8
commit
4236f14955
|
@ -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
|
||||
{
|
||||
|
|
|
@ -409,7 +409,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
|||
{
|
||||
if (handle)
|
||||
handleWithContext();
|
||||
else
|
||||
else if (getHttpConfiguration().isNotifyRemoteAsyncErrors())
|
||||
getState().asyncError(failure);
|
||||
callback.succeeded();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue