diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index ba4b630f5e01..619c877ee392 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -863,10 +863,12 @@ public void write(byte[] b, int off, int len) throws IOException // Blocking write try { + boolean complete = false; // flush any content from the aggregate if (BufferUtil.hasContent(_aggregate)) { - channelWrite(_aggregate, last && len == 0); + complete = last && len == 0; + channelWrite(_aggregate, complete); // should we fill aggregate again from the buffer? if (len > 0 && !last && len <= _commitSize && len <= maximizeAggregateSpace()) @@ -896,6 +898,10 @@ public void write(byte[] b, int off, int len) throws IOException } channelWrite(view, last); } + else if (last && !complete) + { + channelWrite(BufferUtil.EMPTY_BUFFER, true); + } onWriteComplete(last, null); } @@ -963,12 +969,18 @@ public void write(ByteBuffer buffer) throws IOException { // Blocking write // flush any content from the aggregate + boolean complete = false; if (BufferUtil.hasContent(_aggregate)) - channelWrite(_aggregate, last && len == 0); + { + complete = last && len == 0; + channelWrite(_aggregate, complete); + } // write any remaining content in the buffer directly if (len > 0) channelWrite(buffer, last); + else if (last && !complete) + channelWrite(BufferUtil.EMPTY_BUFFER, true); onWriteComplete(last, null); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java index 5696e01caf5a..36d8d34b7e01 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java @@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.AsyncContext; import javax.servlet.ServletException; @@ -763,6 +764,102 @@ public void setNext(Interceptor interceptor) assertThat(response, containsString("400\tTHIS IS A BIGGER FILE")); } + @Test + public void testEmptyArray() throws Exception + { + AtomicBoolean committed = new AtomicBoolean(); + AbstractHandler handler = new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setStatus(200); + response.getOutputStream().write(new byte[0]); + committed.set(response.isCommitted()); + } + }; + + _swap.setHandler(handler); + handler.start(); + String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(committed.get(), is(false)); + } + + @Test + public void testEmptyArrayKnown() throws Exception + { + AtomicBoolean committed = new AtomicBoolean(); + AbstractHandler handler = new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setStatus(200); + response.setContentLength(0); + response.getOutputStream().write(new byte[0]); + committed.set(response.isCommitted()); + } + }; + + _swap.setHandler(handler); + handler.start(); + String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("Content-Length: 0")); + assertThat(committed.get(), is(true)); + } + + @Test + public void testEmptyBuffer() throws Exception + { + AtomicBoolean committed = new AtomicBoolean(); + AbstractHandler handler = new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setStatus(200); + ((HttpOutput)response.getOutputStream()).write(ByteBuffer.wrap(new byte[0])); + committed.set(response.isCommitted()); + } + }; + + _swap.setHandler(handler); + handler.start(); + String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(committed.get(), is(false)); + } + + @Test + public void testEmptyBufferKnown() throws Exception + { + AtomicBoolean committed = new AtomicBoolean(); + AbstractHandler handler = new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setStatus(200); + response.setContentLength(0); + ((HttpOutput)response.getOutputStream()).write(ByteBuffer.wrap(new byte[0])); + committed.set(response.isCommitted()); + } + }; + + _swap.setHandler(handler); + handler.start(); + String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response, containsString("HTTP/1.1 200 OK")); + assertThat(response, containsString("Content-Length: 0")); + assertThat(committed.get(), is(true)); + } + @Test public void testAggregation() throws Exception {