Issue #4411 - Jetty server spins on incomplete request.

Fixed HttpInput to be in READY state if an error is detected
before the call to setWriteListener().

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2019-12-11 00:11:29 +01:00
parent 0e6a1ce76b
commit 6bbec7f3b0
3 changed files with 99 additions and 12 deletions

View File

@ -343,7 +343,7 @@ public class HttpConfiguration implements Dumpable
}
/**
* @param delay if true, delay the application dispatch until content is available (default false)
* @param delay if true, delays the application dispatch until content is available (defaults to true)
*/
public void setDelayDispatchUntilContent(boolean delay)
{

View File

@ -756,22 +756,29 @@ public class HttpInput extends ServletInputStream implements Runnable
_listener = Objects.requireNonNull(readListener);
Content content = produceNextContext();
if (content != null)
if (isError())
{
_state = ASYNC;
woken = _channelState.onReadReady();
}
else if (_state == EOF)
{
_state = AEOF;
woken = _channelState.onReadEof();
}
else
{
_state = ASYNC;
_channelState.onReadUnready();
_waitingForContent = true;
Content content = produceNextContext();
if (content != null)
{
_state = ASYNC;
woken = _channelState.onReadReady();
}
else if (_state == EOF)
{
_state = AEOF;
woken = _channelState.onReadEof();
}
else
{
_state = ASYNC;
_channelState.onReadUnready();
_waitingForContent = true;
}
}
}
}

View File

@ -43,7 +43,9 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.AsyncContext;
import javax.servlet.ReadListener;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -56,6 +58,7 @@ import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.client.util.OutputStreamContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
@ -63,6 +66,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
@ -1264,4 +1268,80 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
Result result = listener.await(5, TimeUnit.SECONDS);
assertTrue(result.isSucceeded());
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testClientDefersContentServerIdleTimeout(Transport transport) throws Exception
{
// TODO: fix FCGI that is failing this test.
Assumptions.assumeTrue(transport != Transport.FCGI);
init(transport);
CountDownLatch dataLatch = new CountDownLatch(1);
CountDownLatch errorLatch = new CountDownLatch(1);
scenario.start(new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException
{
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(0);
request.getInputStream().setReadListener(new ReadListener()
{
@Override
public void onDataAvailable()
{
dataLatch.countDown();
}
@Override
public void onAllDataRead()
{
dataLatch.countDown();
}
@Override
public void onError(Throwable t)
{
errorLatch.countDown();
response.setStatus(HttpStatus.REQUEST_TIMEOUT_408);
asyncContext.complete();
}
});
}
});
long idleTimeout = 1000;
scenario.setServerIdleTimeout(idleTimeout);
CountDownLatch latch = new CountDownLatch(1);
byte[] bytes = "[{\"key\":\"value\"}]".getBytes(StandardCharsets.UTF_8);
OutputStreamContentProvider content = new OutputStreamContentProvider()
{
@Override
public long getLength()
{
return bytes.length;
}
};
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(content, "application/json;charset=UTF-8")
.onResponseSuccess(response ->
{
assertEquals(HttpStatus.REQUEST_TIMEOUT_408, response.getStatus());
latch.countDown();
})
.send(null);
// Wait for the server to idle timeout.
Thread.sleep(2 * idleTimeout);
assertTrue(errorLatch.await(5, TimeUnit.SECONDS));
// Do not send the content to the server.
assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}