From 05fcd52db319cf691d7b5c90293a07b6aeb89a39 Mon Sep 17 00:00:00 2001 From: Carter Kozak Date: Fri, 14 Jan 2022 20:39:14 -0500 Subject: [PATCH] UNDERTOW-2019: Blocking exchanges can set HEAD response length --- .../io/undertow/io/UndertowOutputStream.java | 11 +++- .../HeadBlockingExchangeTestCase.java | 63 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/io/undertow/server/handlers/HeadBlockingExchangeTestCase.java diff --git a/core/src/main/java/io/undertow/io/UndertowOutputStream.java b/core/src/main/java/io/undertow/io/UndertowOutputStream.java index b013c46ef4..5d2f1c2ee2 100644 --- a/core/src/main/java/io/undertow/io/UndertowOutputStream.java +++ b/core/src/main/java/io/undertow/io/UndertowOutputStream.java @@ -26,6 +26,7 @@ import io.undertow.UndertowMessages; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; +import io.undertow.util.Methods; import org.xnio.Buffers; import io.undertow.connector.ByteBufferPool; import io.undertow.connector.PooledByteBuffer; @@ -327,7 +328,9 @@ public void close() throws IOException { if (anyAreSet(state, FLAG_CLOSED)) return; try { state |= FLAG_CLOSED; - if (anyAreClear(state, FLAG_WRITE_STARTED) && channel == null) { + if (anyAreClear(state, FLAG_WRITE_STARTED) + && channel == null + && !isHeadRequestWithContentLength(exchange)) { if (buffer == null) { exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "0"); } else { @@ -356,6 +359,12 @@ public void close() throws IOException { } } + // Head request handlers may set the content-length response header in lieu of writing bytes + private static boolean isHeadRequestWithContentLength(HttpServerExchange exchange) { + return Methods.HEAD.equals(exchange.getRequestMethod()) + && exchange.getResponseHeaders().contains(Headers.CONTENT_LENGTH); + } + private ByteBuffer buffer() { ByteBuffer buffer = this.buffer; if (buffer != null) { diff --git a/core/src/test/java/io/undertow/server/handlers/HeadBlockingExchangeTestCase.java b/core/src/test/java/io/undertow/server/handlers/HeadBlockingExchangeTestCase.java new file mode 100644 index 0000000000..c5a7c4c92f --- /dev/null +++ b/core/src/test/java/io/undertow/server/handlers/HeadBlockingExchangeTestCase.java @@ -0,0 +1,63 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2022 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.undertow.server.handlers; + +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.HttpClientUtils; +import io.undertow.testutils.TestHttpClient; +import io.undertow.util.Headers; +import io.undertow.util.StatusCodes; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpHead; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; + +/** + * Test that {@code HEAD} requests can be used with a blocking exchange, setting response + * content-length without unnecessarily writing bytes. + * + * @author Carter Kozak + */ +@RunWith(DefaultServer.class) +public class HeadBlockingExchangeTestCase { + + @BeforeClass + public static void setup() { + DefaultServer.setRootHandler(new BlockingHandler( + exchange -> exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, "" + 100))); + } + + @Test + public void sendHttpHead() throws IOException { + HttpHead head = new HttpHead(DefaultServer.getDefaultServerURL()); + TestHttpClient client = new TestHttpClient(); + try { + HttpResponse result = client.execute(head); + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + Assert.assertEquals("", HttpClientUtils.readResponse(result)); + Assert.assertEquals("100", result.getFirstHeader("Content-Length").getValue()); + } finally { + client.getConnectionManager().shutdown(); + } + } +}