Skip to content

Commit 00b3174

Browse files
committed
Move the flush-before-close-on-exception regression test to NetTest
The fix is at the transport layer (VertxHandler/VertxConnection) and is not HTTP specific, so exercise it over plain TCP rather than HTTP. Drop Http1xTest#testRequestDecompressionInvalidBodyDeliversErrorResponse and add NetTest#testFlushPendingWriteOnExceptionClose, which parks an unflushed write on a NetServer connection, triggers a caught exception that closes it, and asserts the client still receives the bytes before the close. Signed-off-by: Alexandru Salajan <alexandru17@gmail.com>
1 parent 15d6268 commit 00b3174

2 files changed

Lines changed: 27 additions & 44 deletions

File tree

vertx-core/src/test/java/io/vertx/tests/http/Http1xTest.java

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,50 +2875,6 @@ public void testContentDecompression() throws Exception {
28752875
.await();
28762876
}
28772877

2878-
@Test
2879-
// Regression test: when an inbound request body fails to decompress, the application's
2880-
// exception handler must still be able to deliver an error response to the client. Before
2881-
// the VertxHandler#exceptionCaught flush-before-close fix, the response written from the
2882-
// exception handler was discarded and the client only saw a connection reset.
2883-
public void testRequestDecompressionInvalidBodyDeliversErrorResponse() throws Exception {
2884-
server = vertx.createHttpServer(new HttpServerOptions().setDecompressionSupported(true));
2885-
// A valid gzip header (the first 10 bytes) followed by a corrupted deflate payload, so that
2886-
// Netty's HttpContentDecompressor accepts the stream as gzip and then fails while decoding it.
2887-
byte[] corrupted = TestUtils.compressGzip(TestUtils.randomAlphaString(256));
2888-
for (int i = 10; i < corrupted.length; i++) {
2889-
corrupted[i] = (byte) ~corrupted[i];
2890-
}
2891-
server.requestHandler(req -> {
2892-
req.exceptionHandler(err -> {
2893-
if (!req.response().ended()) {
2894-
req.response().setStatusCode(400).end("decode-failed");
2895-
}
2896-
});
2897-
// Should not be reached for a malformed body.
2898-
req.bodyHandler(body -> req.response().end());
2899-
});
2900-
startServer(testAddress);
2901-
NetClient netClient = vertx.createNetClient();
2902-
CompletableFuture<String> result = new CompletableFuture<>();
2903-
netClient.connect(testAddress).onComplete(TestUtils.onSuccess(so -> {
2904-
Buffer respBuff = Buffer.buffer();
2905-
so.handler(respBuff::appendBuffer);
2906-
so.closeHandler(v -> result.complete(respBuff.toString()));
2907-
so.exceptionHandler(result::completeExceptionally);
2908-
Buffer request = Buffer.buffer()
2909-
.appendString("PUT /somepath HTTP/1.1\r\n")
2910-
.appendString("Host: localhost\r\n")
2911-
.appendString("Content-Encoding: gzip\r\n")
2912-
.appendString("Content-Length: " + corrupted.length + "\r\n")
2913-
.appendString("\r\n")
2914-
.appendBytes(corrupted);
2915-
so.write(request);
2916-
}));
2917-
String resp = result.get(20, TimeUnit.SECONDS);
2918-
assertTrue("Expected the response to start with \"HTTP/1.1 400\" but got: [" + resp + "]", resp.startsWith("HTTP/1.1 400"));
2919-
assertTrue("Expected the error body in the response but got: [" + resp + "]", resp.contains("decode-failed"));
2920-
}
2921-
29222878
@Test
29232879
public void testResetClientRequestNotYetSent(Checkpoint checkpoint) throws Exception {
29242880
testResetClientRequestNotYetSent(checkpoint, false, false);

vertx-core/src/test/java/io/vertx/tests/net/NetTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import io.vertx.core.json.JsonObject;
4141
import io.vertx.core.net.*;
4242
import io.vertx.core.net.impl.HAProxyMessageCompletionHandler;
43+
import io.vertx.core.net.impl.VertxConnection;
4344
import io.vertx.core.net.impl.VertxHandler;
4445
import io.vertx.core.net.impl.tcp.CleanableNetClient;
4546
import io.vertx.core.internal.net.NetServerInternal;
@@ -4641,4 +4642,30 @@ public void testCloseServerUnwritableSocket(Checkpoint checkpoint1, Checkpoint c
46414642
TestUtils.assertWaitUntil(closing::get);
46424643
TestUtils.assertWaitUntil(closed::get);
46434644
}
4645+
4646+
@Test
4647+
public void testFlushPendingWriteOnExceptionClose() throws Exception {
4648+
server.connectHandler(so -> {
4649+
so.exceptionHandler(err -> {});
4650+
so.handler(data -> {
4651+
VertxConnection conn = (VertxConnection) so;
4652+
// A response queued on the connection but not yet flushed (the state produced when a
4653+
// response is written while a read is in progress), then a pipeline failure that tears
4654+
// the connection down.
4655+
conn.unsafeWrite(Unpooled.copiedBuffer("pending-response", StandardCharsets.UTF_8), false);
4656+
conn.channelHandlerContext().pipeline().fireExceptionCaught(new RuntimeException("boom"));
4657+
});
4658+
});
4659+
startServer();
4660+
Buffer received = Buffer.buffer();
4661+
CompletableFuture<String> result = new CompletableFuture<>();
4662+
NetSocket socket = client.connect(testAddress).await();
4663+
socket.handler(received::appendBuffer);
4664+
socket.closeHandler(v -> result.complete(received.toString()));
4665+
socket.exceptionHandler(result::completeExceptionally);
4666+
socket.write("ping").await();
4667+
// With the fix the pending response is flushed before the connection closes; without it the
4668+
// client only sees the close (Netty discards the unflushed entry).
4669+
assertEquals("pending-response", result.get(20, TimeUnit.SECONDS));
4670+
}
46444671
}

0 commit comments

Comments
 (0)