Skip to content

Commit

Permalink
Merge pull request #1480 from TomasHofman/JBEAP-24861
Browse files Browse the repository at this point in the history
UNDERTOW-2275 Suspend reads before terminating a request to cleanup r…
  • Loading branch information
fl4via committed Jun 14, 2023
2 parents 17e61a3 + 2538532 commit b30b6d8
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 0 deletions.
Expand Up @@ -1341,6 +1341,7 @@ void terminateRequest() {
return;
}
if (requestChannel != null) {
requestChannel.suspendReads();
requestChannel.requestDone();
}
this.state = oldVal | FLAG_REQUEST_TERMINATED;
Expand Down
73 changes: 73 additions & 0 deletions core/src/test/java/io/undertow/server/ReadTimeoutTestCase.java
Expand Up @@ -25,16 +25,30 @@
import java.io.StringWriter;
import java.net.SocketException;
import java.nio.channels.Channel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import io.undertow.io.IoCallback;
import io.undertow.io.Sender;
import io.undertow.server.handlers.BlockingHandler;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.server.handlers.form.FormEncodedDataDefinition;
import io.undertow.server.handlers.form.FormParserFactory;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.HttpOneOnly;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.Headers;
import io.undertow.util.StringWriteChannelListener;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -56,6 +70,9 @@
@HttpOneOnly
public class ReadTimeoutTestCase {

private static final Logger LOG = Logger.getLogger(ReadTimeoutTestCase.class);
private static final int FORM_PARAM_LENGTH = 1024 * 16;

private volatile Exception exception;

@DefaultServer.BeforeServerStarts
Expand Down Expand Up @@ -157,6 +174,62 @@ public long getContentLength() {
}
}

/**
* This verifies that read-timeout is cleaned-up after request is completely parsed. I.e. slow request processing
* happening after the request has been fully read should not trigger read-timeout.
*/
@Test
public void testReadTimeoutWithSlowResponder() throws IOException {
DefaultServer.setRootHandler(new BlockingHandler((final HttpServerExchange exchange) -> {
FormParserFactory parserFactory = new FormParserFactory.Builder()
.addParser(new FormEncodedDataDefinition())
.build();

// Reads and parses the request.
try (FormDataParser formDataParser = parserFactory.createParser(exchange)) {
FormData formData = formDataParser.parseBlocking();
FormData.FormValue test = formData.getFirst("test");
Assert.assertNotNull(test);
Assert.assertEquals(FORM_PARAM_LENGTH, test.getValue().length());
}

// Write some response with delays, to breach read-timeout if it hasn't been cleaned-up.
Sender responseSender = exchange.getResponseSender();
for (int i = 0; i < 5; i++) {
Thread.sleep(200);
responseSender.send("*", new IoCallback() {
@Override
public void onComplete(HttpServerExchange exchange, Sender sender) {
LOG.debug("Sent '*'");
}

@Override
public void onException(HttpServerExchange exchange, Sender sender, IOException exception) {
LOG.warn("Failed to write data to response channel.", exception);
}
});
}
}));

try (TestHttpClient client = new TestHttpClient()) {
HttpPost post = new HttpPost(DefaultServer.getDefaultServerURL());

// Generate 2 KB form param value.
List<NameValuePair> nameValuePairs = new ArrayList<>();
StringBuilder sb = new StringBuilder(FORM_PARAM_LENGTH);
for (int i = 0; i < FORM_PARAM_LENGTH; i++) {
sb.append('a');
}
nameValuePairs.add(new BasicNameValuePair("test", sb.toString()));
post.setEntity(new UrlEncodedFormEntity(nameValuePairs));

// Request should succeed.
client.execute(post);
} catch (NoHttpResponseException e) {
Assert.fail("No response was received, this was presumably caused by read-timeout closing the connection.");
}
}

// TODO move this to an utility class
private String getExceptionDescription(Throwable exception) {
try (StringWriter sw = new StringWriter();
Expand Down

0 comments on commit b30b6d8

Please sign in to comment.