// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.server; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.handler.AbstractHandler; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; //TODO: reset buffer tests //TODO: add protocol specific tests for connection: close and/or chunking @RunWith(Parameterized.class) public class HttpManyWaysToCommitTest extends AbstractHttpTest { @Parameterized.Parameters(name = "{0}") public static Collection<Object[]> data() { Object[][] data = new Object[][]{{HttpVersion.HTTP_1_0.asString()}, {HttpVersion.HTTP_1_1.asString()}}; return Arrays.asList(data); } public HttpManyWaysToCommitTest(String httpVersion) { super(httpVersion); } @Test public void testHandlerDoesNotSetHandled() throws Exception { server.setHandler(new DoesNotSetHandledHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(404)); } @Test public void testHandlerDoesNotSetHandledAndThrow() throws Exception { server.setHandler(new DoesNotSetHandledHandler(true)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(500)); } private class DoesNotSetHandledHandler extends ThrowExceptionOnDemandHandler { private DoesNotSetHandledHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(false); // not needed, but lets be explicit about what the test does super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testHandlerSetsHandledTrueOnly() throws Exception { server.setHandler(new OnlySetHandledHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); if (HttpVersion.HTTP_1_1.asString().equals(httpVersion)) assertHeader(response, "content-length", "0"); } @Test public void testHandlerSetsHandledTrueOnlyAndThrow() throws Exception { server.setHandler(new OnlySetHandledHandler(true)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(500)); } private class OnlySetHandledHandler extends ThrowExceptionOnDemandHandler { private OnlySetHandledHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testHandlerSetsHandledAndWritesSomeContent() throws Exception { server.setHandler(new SetHandledWriteSomeDataHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobar"); assertHeader(response, "content-length", "6"); } @Test public void testHandlerSetsHandledAndWritesSomeContentAndThrow() throws Exception { server.setHandler(new SetHandledWriteSomeDataHandler(true)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(500)); assertThat("response body", response.getContent(), not(is("foobar"))); } private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler { private SetHandledWriteSomeDataHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.getWriter().write("foobar"); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testHandlerExplicitFlush() throws Exception { server.setHandler(new ExplicitFlushHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobar"); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } @Test public void testHandlerExplicitFlushAndThrow() throws Exception { server.setHandler(new ExplicitFlushHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Since the 200 was committed, the 500 did not get the chance to be written assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foobar")); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } private class ExplicitFlushHandler extends ThrowExceptionOnDemandHandler { private ExplicitFlushHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.getWriter().write("foobar"); response.flushBuffer(); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testHandledAndFlushWithoutContent() throws Exception { server.setHandler(new SetHandledAndFlushWithoutContentHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } @Test public void testHandledAndFlushWithoutContentAndThrow() throws Exception { server.setHandler(new SetHandledAndFlushWithoutContentHandler(true)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } private class SetHandledAndFlushWithoutContentHandler extends ThrowExceptionOnDemandHandler { private SetHandledAndFlushWithoutContentHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.flushBuffer(); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testHandledWriteFlushWriteMore() throws Exception { server.setHandler(new WriteFlushWriteMoreHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobar"); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } @Test public void testHandledWriteFlushWriteMoreAndThrow() throws Exception { server.setHandler(new WriteFlushWriteMoreHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Since the 200 was committed, the 500 did not get the chance to be written assertThat("response code", response.getStatus(), is(200)); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } private class WriteFlushWriteMoreHandler extends ThrowExceptionOnDemandHandler { private WriteFlushWriteMoreHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.getWriter().write("foo"); response.flushBuffer(); response.getWriter().write("bar"); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testHandledOverflow() throws Exception { server.setHandler(new OverflowHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobar"); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } @Test public void testHandledOverflow2() throws Exception { server.setHandler(new Overflow2Handler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobarfoobar"); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } @Test public void testHandledOverflow3() throws Exception { server.setHandler(new Overflow3Handler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobarfoobar"); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } @Test public void testHandledBufferOverflowAndThrow() throws Exception { server.setHandler(new OverflowHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Response was committed when we throw, so 200 expected assertThat("response code", response.getStatus(), is(200)); assertResponseBody(response, "foobar"); if (!"HTTP/1.0".equals(httpVersion)) assertHeader(response, "transfer-encoding", "chunked"); } private class OverflowHandler extends ThrowExceptionOnDemandHandler { private OverflowHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setBufferSize(4); response.getWriter().write("foobar"); super.doNonErrorHandle(target, baseRequest, request, response); } } private class Overflow2Handler extends ThrowExceptionOnDemandHandler { private Overflow2Handler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setBufferSize(8); response.getWriter().write("fo"); response.getWriter().write("obarfoobar"); super.doNonErrorHandle(target, baseRequest, request, response); } } private class Overflow3Handler extends ThrowExceptionOnDemandHandler { private Overflow3Handler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setBufferSize(8); response.getWriter().write("fo"); response.getWriter().write("ob"); response.getWriter().write("ar"); response.getWriter().write("fo"); response.getWriter().write("ob"); response.getWriter().write("ar"); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testSetContentLengthFlushAndWriteInsufficientBytes() throws Exception { server.setHandler(new SetContentLengthAndWriteInsufficientBytesHandler(true)); server.start(); HttpTester.Response response = executeRequest(); System.out.println(response.toString()); assertThat("response code", response.getStatus(), is(200)); assertHeader(response, "content-length", "6"); byte content[] = response.getContentBytes(); assertThat("content bytes", content.length, is(0)); assertTrue("response eof", response.isEarlyEOF()); } @Test public void testSetContentLengthAndWriteInsufficientBytes() throws Exception { server.setHandler(new SetContentLengthAndWriteInsufficientBytesHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response is error", response.getStatus(), is(500)); assertFalse("response not eof", response.isEarlyEOF()); } @Test public void testSetContentLengthAndFlushWriteInsufficientBytes() throws Exception { server.setHandler(new SetContentLengthAndWriteInsufficientBytesHandler(true)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response has no status", response.getStatus(), is(200)); assertTrue("response eof", response.isEarlyEOF()); } @Test public void testSetContentLengthAndWriteExactlyThatAmountOfBytes() throws Exception { server.setHandler(new SetContentLengthAndWriteThatAmountOfBytesHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foo")); assertHeader(response, "content-length", "3"); } @Test public void testSetContentLengthAndWriteExactlyThatAmountOfBytesAndThrow() throws Exception { server.setHandler(new SetContentLengthAndWriteThatAmountOfBytesHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Setting the content-length and then writing the bytes commits the response assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foo")); } private class SetContentLengthAndWriteInsufficientBytesHandler extends AbstractHandler { boolean flush; private SetContentLengthAndWriteInsufficientBytesHandler(boolean flush) { this.flush = flush; } @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setContentLength(6); if (flush) response.flushBuffer(); response.getWriter().write("foo"); } } private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler { private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setContentLength(3); response.getWriter().write("foo"); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testSetContentLengthAndWriteMoreBytes() throws Exception { server.setHandler(new SetContentLengthAndWriteMoreBytesHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foo")); assertHeader(response, "content-length", "3"); } @Test public void testSetContentLengthAndWriteMoreAndThrow() throws Exception { server.setHandler(new SetContentLengthAndWriteMoreBytesHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Setting the content-length and then writing the bytes commits the response assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foo")); } private class SetContentLengthAndWriteMoreBytesHandler extends ThrowExceptionOnDemandHandler { private SetContentLengthAndWriteMoreBytesHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.setContentLength(3); // Only "foo" will get written and "bar" will be discarded response.getWriter().write("foobar"); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testWriteAndSetContentLength() throws Exception { server.setHandler(new WriteAndSetContentLengthHandler(false)); server.start(); HttpTester.Response response = executeRequest(); assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foo")); assertHeader(response, "content-length", "3"); } @Test public void testWriteAndSetContentLengthAndThrow() throws Exception { server.setHandler(new WriteAndSetContentLengthHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Writing the bytes and then setting the content-length commits the response assertThat("response code", response.getStatus(), is(200)); assertThat("response body", response.getContent(), is("foo")); } private class WriteAndSetContentLengthHandler extends ThrowExceptionOnDemandHandler { private WriteAndSetContentLengthHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.getWriter().write("foo"); response.setContentLength(3); super.doNonErrorHandle(target, baseRequest, request, response); } } @Test public void testWriteAndSetContentLengthTooSmall() throws Exception { server.setHandler(new WriteAndSetContentLengthTooSmallHandler(false)); server.start(); HttpTester.Response response = executeRequest(); // Setting a content-length too small throws an IllegalStateException assertThat("response code", response.getStatus(), is(500)); assertThat("response body", response.getContent(), not(is("foo"))); } @Test public void testWriteAndSetContentLengthTooSmallAndThrow() throws Exception { server.setHandler(new WriteAndSetContentLengthTooSmallHandler(true)); server.start(); HttpTester.Response response = executeRequest(); // Setting a content-length too small throws an IllegalStateException assertThat("response code", response.getStatus(), is(500)); assertThat("response body", response.getContent(), not(is("foo"))); } private class WriteAndSetContentLengthTooSmallHandler extends ThrowExceptionOnDemandHandler { private WriteAndSetContentLengthTooSmallHandler(boolean throwException) { super(throwException); } @Override public void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { baseRequest.setHandled(true); response.getWriter().write("foobar"); response.setContentLength(3); super.doNonErrorHandle(target, baseRequest, request, response); } } }