diff options
4 files changed, 867 insertions, 256 deletions
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index b31d448737..b55465c375 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -69,8 +69,6 @@ public abstract class HttpChannel __currentChannel.set(channel); } - - private final Server _server; private final Connection _connection; private final HttpURI _uri; diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpFiveWaysToCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpFiveWaysToCommitTest.java deleted file mode 100644 index e22412bbff..0000000000 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpFiveWaysToCommitTest.java +++ /dev/null @@ -1,194 +0,0 @@ -package org.eclipse.jetty.server; -//======================================================================== -//Copyright (c) 1999-2009 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. -//======================================================================== - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URISyntaxException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.server.handler.AbstractHandler; -import org.eclipse.jetty.util.IO; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -/** - * Resource Handler test - * <p/> - * TODO: increase the testing going on here - */ -public class HttpFiveWaysToCommitTest -{ - private static Server server; - private static SelectChannelConnector connector; - - - @Before - public void setUp() throws Exception - { - server = new Server(); - connector = new SelectChannelConnector(server); - server.setConnectors(new Connector[]{connector}); - } - - /* ------------------------------------------------------------ */ - @After - public void tearDown() throws Exception - { - server.stop(); - } - - @Test - public void testHandlerSetsHandledTrueOnly() throws Exception - { - server.setHandler(new OnlySetHandledHandler()); - server.start(); - - StringWriter writer = executeRequest(HttpStatus.OK_200); - - System.out.println("RESPONSE: " + writer.toString()); - } - - - private class OnlySetHandledHandler extends AbstractHandler - { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - } - } - - @Test - public void testHandlerExplicitFlush() throws Exception - { - server.setHandler(new ExplicitFlushHandler()); - server.start(); - - StringWriter writer = executeRequest(HttpStatus.OK_200); - - System.out.println("RESPONSE: " + writer.toString()); - } - - private class ExplicitFlushHandler extends AbstractHandler - { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - response.getWriter().write("foobar"); - response.flushBuffer(); - } - } - - @Test - public void testHandlerDoesNotSetHandled() throws Exception - { - server.setHandler(new DoesNotSetHandledHandler()); - server.start(); - - StringWriter writer = executeRequest(HttpStatus.NOT_FOUND_404); - - System.out.println("RESPONSE: " + writer.toString()); - } - - private class DoesNotSetHandledHandler extends AbstractHandler - { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(false); - } - } - - @Test - public void testCommitWithMoreDataToWrite() throws Exception - { - server.setHandler(new CommitResponseWithMoreDataToWriteHandler()); - server.start(); - - StringWriter writer = executeRequest(HttpStatus.OK_200); - - System.out.println("RESPONSE: " + writer.toString()); - } - - private class CommitResponseWithMoreDataToWriteHandler extends AbstractHandler - { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - response.getWriter().write("foo"); - response.flushBuffer(); - response.getWriter().write("bar"); - } - } - - @Test - public void testBufferOverflow() throws Exception - { - server.setHandler(new OverflowHandler()); - server.start(); - - StringWriter writer = executeRequest(HttpStatus.OK_200); - - System.out.println("RESPONSE: " + writer.toString()); - } - - private class OverflowHandler extends AbstractHandler - { - @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException - { - baseRequest.setHandled(true); - response.setContentLength(2); - response.getWriter().write("foo"); - } - } - - private StringWriter executeRequest(int expectedStatus) throws URISyntaxException, IOException - { - URI uri = new URI("http://localhost:" + connector.getLocalPort() + "/"); - HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection(); - connection.connect(); - - for (String header : connection.getHeaderFields().keySet()) - { - System.out.println(header + ": " + connection.getHeaderFields().get(header)); - } - int responseCode = connection.getResponseCode(); - assertThat("return code is 200 ok", responseCode, is(expectedStatus)); - - StringWriter writer = new StringWriter(); - if (responseCode == HttpStatus.OK_200) - { - InputStream inputStream = connection.getInputStream(); - InputStreamReader reader = new InputStreamReader(inputStream); - IO.copy(reader, writer); - } - - return writer; - } - -} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java new file mode 100644 index 0000000000..5e8cb34bc4 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java @@ -0,0 +1,744 @@ +//======================================================================== +//Copyright (c) 1999-2009 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 java.io.BufferedReader; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +//TODO: reset buffer tests +@RunWith(value = Parameterized.class) +public class HttpManyWaysToCommitTest +{ + private static Server server; + private static SelectChannelConnector connector; + private String httpVersion; + + @Parameterized.Parameters + public static Collection<Object[]> data() + { + Object[][] data = new Object[][]{{"HTTP/1.0"}, {"HTTP/1.1"}}; + return Arrays.asList(data); + } + + public HttpManyWaysToCommitTest(String httpVersion) + { + this.httpVersion = httpVersion; + } + + @Before + public void setUp() throws Exception + { + server = new Server(); + connector = new SelectChannelConnector(server); + server.setConnectors(new Connector[]{connector}); + } + + /* ------------------------------------------------------------ */ + @After + public void tearDown() throws Exception + { + server.stop(); + } + + @Test + public void testHandlerDoesNotSetHandled() throws Exception + { + server.setHandler(new DoesNotSetHandledHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 404", response.getCode(), is("404")); + } + + @Test + public void testHandlerDoesNotSetHandledAndThrow() throws Exception + { + server.setHandler(new DoesNotSetHandledHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 500", response.getCode(), is("500")); + } + + private class DoesNotSetHandledHandler extends ThrowExceptionOnDemandHandler + { + private DoesNotSetHandledHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(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.handle(target, baseRequest, request, response); + } + } + + @Test + public void testHandlerSetsHandledTrueOnly() throws Exception + { + server.setHandler(new OnlySetHandledHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "content-length", "0"); + } + + @Test + public void testHandlerSetsHandledTrueOnlyAndThrow() throws Exception + { + server.setHandler(new OnlySetHandledHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 500", response.getCode(), is("500")); + } + + + private class OnlySetHandledHandler extends ThrowExceptionOnDemandHandler + { + private OnlySetHandledHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testHandlerSetsHandledAndWritesSomeContent() throws Exception + { + server.setHandler(new SetHandledWriteSomeDataHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "content-length", "6"); + } + + @Test + public void testHandlerSetsHandledAndWritesSomeContentAndThrow() throws Exception + { + server.setHandler(new SetHandledWriteSomeDataHandler(true)); + server.start(); + + Response response = executeRequest(); + + // This assertion is currently red. However I think it should be green. Nothing has been flushed to the + // client yet, when we throw an exception in the handler. So I expect HttpChannel to send a 500. + // Fails in HttpConnection line 619/620 + assertThat("response code is 500", response.getCode(), is("500")); + } + + private class SetHandledWriteSomeDataHandler extends ThrowExceptionOnDemandHandler + { + private SetHandledWriteSomeDataHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.getWriter().write("foobar"); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testHandlerExplicitFlush() throws Exception + { + server.setHandler(new ExplicitFlushHandler(false)); + server.start(); + + Response response = executeRequest(); + + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "transfer-encoding", "chunked"); + } + + @Test + public void testHandlerExplicitFlushAndThrow() throws Exception + { + server.setHandler(new ExplicitFlushHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "transfer-encoding", "chunked"); + } + + private class ExplicitFlushHandler extends ThrowExceptionOnDemandHandler + { + private ExplicitFlushHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.getWriter().write("foobar"); + response.flushBuffer(); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testHandledAndFlushWithoutContent() throws Exception + { + server.setHandler(new SetHandledAndFlushWithoutContentHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "transfer-encoding", "chunked"); + } + + @Test + public void testHandledAndFlushWithoutContentAndThrow() throws Exception + { + server.setHandler(new SetHandledAndFlushWithoutContentHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "transfer-encoding", "chunked"); + } + + private class SetHandledAndFlushWithoutContentHandler extends ThrowExceptionOnDemandHandler + { + private SetHandledAndFlushWithoutContentHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.flushBuffer(); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testWriteFlushWriteMore() throws Exception + { + server.setHandler(new WriteFlushWriteMoreHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "transfer-encoding", "chunked"); + } + + @Test + public void testWriteFlushWriteMoreAndThrow() throws Exception + { + server.setHandler(new WriteFlushWriteMoreHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertHeader(response, "transfer-encoding", "chunked"); + } + + private class WriteFlushWriteMoreHandler extends ThrowExceptionOnDemandHandler + { + private WriteFlushWriteMoreHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(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.handle(target, baseRequest, request, response); + } + } + + @Test + public void testBufferOverflow() throws Exception + { + server.setHandler(new OverflowHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertResponseBody(response, "foobar"); + assertHeader(response, "content-length", "6"); + } + + @Test + public void testBufferOverflowAndThrow() throws Exception + { + server.setHandler(new OverflowHandler(true)); + server.start(); + + Response response = executeRequest(); + + // response not committed when we throw, so 500 expected + assertThat("response code is 500", response.getCode(), is("500")); + } + + private class OverflowHandler extends ThrowExceptionOnDemandHandler + { + private OverflowHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setBufferSize(3); + response.getWriter().write("foobar"); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testSetContentLengthAndWriteExactlyThatAmountOfBytes() throws Exception + { + server.setHandler(new SetContentLengthAndWriteThatAmountOfBytesHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertThat("response body is foo", response.getBody(), is("foo")); + assertHeader(response, "content-length", "3"); + + } + + @Test + public void testSetContentLengthAndWriteExactlyThatAmountOfBytesAndThrow() throws Exception + { + server.setHandler(new SetContentLengthAndWriteThatAmountOfBytesHandler(true)); + server.start(); + + Response response = executeRequest(); + + //TODO: should we expect 500 here? + assertThat("response code is 200", response.getCode(), is("200")); + assertThat("response body is foo", response.getBody(), is("foo")); + assertHeader(response, "content-length", "3"); + } + + private class SetContentLengthAndWriteThatAmountOfBytesHandler extends ThrowExceptionOnDemandHandler + { + private SetContentLengthAndWriteThatAmountOfBytesHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setContentLength(3); + response.getWriter().write("foo"); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testSetContentLengthAndWriteMoreBytes() throws Exception + { + server.setHandler(new SetContentLengthAndWriteMoreBytesHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + // TODO: seems like jetty truncates the body when content-length is reached?! Is this correct and desired + // behaviour? + assertThat("response body is foo", response.getBody(), is("foobar")); + assertHeader(response, "content-length", "6"); + } + + @Test + public void testSetContentLengthAndWriteMoreAndThrow() throws Exception + { + server.setHandler(new SetContentLengthAndWriteMoreBytesHandler(true)); + server.start(); + + Response response = executeRequest(); + + // TODO: we throw before response is committed. should we expect 500? + assertThat("response code is 200", response.getCode(), is("200")); + // TODO: seems like jetty truncates the body when content-length is reached?! Is this correct and desired + // behaviour? + assertThat("response body is foo", response.getBody(), is("foobar")); + assertHeader(response, "content-length", "6"); + } + + private class SetContentLengthAndWriteMoreBytesHandler extends ThrowExceptionOnDemandHandler + { + private SetContentLengthAndWriteMoreBytesHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setContentLength(3); + response.getWriter().write("foobar"); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testWriteAndSetContentLength() throws Exception + { + server.setHandler(new WriteAndSetContentLengthHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + //TODO: jetty ignores setContentLength and sends transfer-encoding header. Correct? + assertHeader(response,"transfer-encoding","chunked"); + } + + @Test + public void testWriteAndSetContentLengthAndThrow() throws Exception + { + server.setHandler(new WriteAndSetContentLengthHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + } + + private class WriteAndSetContentLengthHandler extends ThrowExceptionOnDemandHandler + { + private WriteAndSetContentLengthHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.getWriter().write("foo"); + response.flushBuffer(); + response.setContentLength(3); + super.handle(target, baseRequest, request, response); + } + } + + @Test + public void testWriteAndSetContentLengthTooSmall() throws Exception + { + server.setHandler(new WriteAndSetContentLengthTooSmallHandler(false)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + assertResponseBody(response, "foobar"); + // TODO: once flushed setting contentLength is ignored and chunked is used. Correct? + assertHeader(response,"transfer-encoding","chunked"); + } + + @Test + public void testWriteAndSetContentLengthTooSmallAndThrow() throws Exception + { + server.setHandler(new WriteAndSetContentLengthTooSmallHandler(true)); + server.start(); + + Response response = executeRequest(); + + assertThat("response code is 200", response.getCode(), is("200")); + } + + private class WriteAndSetContentLengthTooSmallHandler extends ThrowExceptionOnDemandHandler + { + private WriteAndSetContentLengthTooSmallHandler(boolean throwException) + { + super(throwException); + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.getWriter().write("foobar"); + response.flushBuffer(); + response.setContentLength(3); + super.handle(target, baseRequest, request, response); + } + } + + private Response executeRequest() throws URISyntaxException, IOException + { + Socket socket = new Socket("localhost", connector.getLocalPort()); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); + String request = "GET / " + httpVersion + "\r\n"; + + writer.write(request); + writer.write("Host: localhost"); + writer.println("\r\n"); + writer.flush(); + + Response response = readResponse(reader); + System.out.println(response);//TODO: remove + return response; + } + + private Response readResponse(BufferedReader reader) throws IOException + { + // Simplified parser for HTTP responses + String line = reader.readLine(); + if (line == null) + throw new EOFException(); + Matcher responseLine = Pattern.compile(httpVersion + "\\s+(\\d+)").matcher(line); + assertThat("http version returned matches request version: " + httpVersion, responseLine.lookingAt(), is(true)); + String code = responseLine.group(1); + + Map<String, String> headers = new LinkedHashMap<>(); + while ((line = reader.readLine()) != null) + { + if (line.trim().length() == 0) + break; + + parseHeader(line, headers); + } + + StringBuilder body; + if (headers.containsKey("content-length")) + { + body = parseContentLengthDelimitedBody(reader, headers); + } + else if ("chunked".equals(headers.get("transfer-encoding"))) + { + body = parseChunkedBody(reader); + } + else + { + body = parseEOFDelimitedBody(reader, headers); + } + + return new Response(code, headers, body.toString().trim()); + } + + private void parseHeader(String line, Map<String, String> headers) + { + Matcher header = Pattern.compile("([^:]+):\\s*(.*)").matcher(line); + assertTrue(header.lookingAt()); + String headerName = header.group(1); + String headerValue = header.group(2); + headers.put(headerName.toLowerCase(), headerValue.toLowerCase()); + } + + private StringBuilder parseContentLengthDelimitedBody(BufferedReader reader, Map<String, String> headers) throws IOException + { + StringBuilder body; + int readLen = 0; + int length = Integer.parseInt(headers.get("content-length")); + body = new StringBuilder(length); + try + { + //TODO: UTF-8 reader from joakim + for (int i = 0; i < length; ++i) + { + char c = (char)reader.read(); + body.append(c); + readLen++; + } + + } + catch (SocketTimeoutException e) + { + System.err.printf("Read %,d bytes (out of an expected %,d bytes)%n", readLen, length); + throw e; + } + return body; + } + + private StringBuilder parseChunkedBody(BufferedReader reader) throws IOException + { + StringBuilder body; + String line; + body = new StringBuilder(64 * 1024); + while ((line = reader.readLine()) != null) + { + if ("0".equals(line)) + { + line = reader.readLine(); + assertThat("There's no more content after as 0 indicated the final chunk", line, is("")); + break; + } + + int length = Integer.parseInt(line, 16); + //TODO: UTF-8 reader from joakim + for (int i = 0; i < length; ++i) + { + char c = (char)reader.read(); + body.append(c); + } + line = reader.readLine(); + assertThat("chunk is followed by an empty line", line, is("")); //TODO: is this right? + } + return body; + } + + private StringBuilder parseEOFDelimitedBody(BufferedReader reader, Map<String, String> headers) throws IOException + { + StringBuilder body; + if ("HTTP/1.1".equals(httpVersion)) + assertThat("if no content-length or transfer-encoding header is set, " + + "connection: close header must be set", headers.get("connection"), + is("close")); + + // read until EOF + body = new StringBuilder(); + while (true) + { + //TODO: UTF-8 reader from joakim + int read = reader.read(); + if (read == -1) + break; + char c = (char)read; + body.append(c); + } + return body; + } + + private void assertResponseBody(Response response, String expectedResponseBody) + { + assertThat("response body is" + expectedResponseBody, response.getBody(), is(expectedResponseBody)); + } + + private void assertHeader(Response response, String headerName, String expectedValue) + { + assertThat(headerName + "=" + expectedValue, response.getHeaders().get(headerName), is(expectedValue)); + } + + private class Response + { + private final String code; + private final Map<String, String> headers; + private final String body; + + private Response(String code, Map<String, String> headers, String body) + { + this.code = code; + this.headers = headers; + this.body = body; + } + + public String getCode() + { + return code; + } + + public Map<String, String> getHeaders() + { + return headers; + } + + public String getBody() + { + return body; + } + + @Override + public String toString() + { + return "Response{" + + "code='" + code + '\'' + + ", headers=" + headers + + ", body='" + body + '\'' + + '}'; + } + } + + private class ThrowExceptionOnDemandHandler extends AbstractHandler + { + private final boolean throwException; + + private ThrowExceptionOnDemandHandler(boolean throwException) + { + this.throwException = throwException; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (throwException) + throw new IllegalStateException("explicit thrown on demand"); + } + } + +} diff --git a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java index fa29394c51..130048b3e8 100644 --- a/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java +++ b/jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnectionFactory.java @@ -96,7 +96,6 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect HTTPChannelOverSPDY channel = new HTTPChannelOverSPDY(connector.getServer(), endPoint.getConnection(), stream); stream.setAttribute(CHANNEL_ATTRIBUTE, channel); - Headers headers = synInfo.getHeaders(); if (headers.isEmpty()) @@ -104,14 +103,11 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect // If the SYN has no headers, they may come later in a HEADERS frame return this; } - - channel.beginRequest(headers); + //TODO: beginRequest does two things, startRequest and close...should be doing one only?! + channel.beginRequest(headers, synInfo.isClose()); if (synInfo.isClose()) - { - channel.endRequest(); return null; - } else return this; } @@ -127,43 +123,7 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect { logger.debug("Received {} on {}", headersInfo, stream); HTTPChannelOverSPDY channel = (HTTPChannelOverSPDY)stream.getAttribute(CHANNEL_ATTRIBUTE); - HttpChannel.EventHandler eventHandler = channel.getEventHandler(); - - for (Headers.Header header : headersInfo.getHeaders()) - { - String name = header.name(); - - // Skip special SPDY headers, unless it's the "host" header - HTTPSPDYHeader specialHeader = HTTPSPDYHeader.from(getVersion(), name); - if (specialHeader != null) - { - if (specialHeader == HTTPSPDYHeader.HOST) - name = "host"; - else - continue; - } - - switch (name) - { - case "connection": - case "keep-alive": - case "proxy-connection": - case "transfer-encoding": - { - // Spec says to ignore these headers - continue; - } - default: - { - HttpHeader httpHeader = HttpHeader.valueOf(header.name()); - // Spec says headers must be single valued - String value = header.value(); - logger.debug("HTTP > {}: {}", name, value); - //TODO: move stuff to HttpOverSPDYChannel? - eventHandler.parsedHeader(httpHeader, header.name(), value); - } - } - } + channel.parseHeaders(headersInfo.getHeaders()); if (headersInfo.isClose()) channel.endRequest(); @@ -230,6 +190,61 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect this.stream = stream; } + @Override + public void handle() + { + switch (state) + { + case INITIAL: + { + break; + } + case REQUEST: + { + logger.debug("handle: REQUEST"); + startRequest(headers); + + updateState(State.HEADERS); + handle(); + break; + } + case HEADERS: + { + parseHeaders(headers); + break; + } + case HEADERS_COMPLETE: + { + getEventHandler().headerComplete(false, false); + } + case CONTENT: + { + //TODO: + // final Buffer buffer = this.buffer; + // if (buffer != null && buffer.length() > 0) + // content(buffer); + break; + } + case FINAL: + { + getEventHandler().messageComplete(0); + super.handle(); + break; + } + case ASYNC: + { + //TODO: + // handleRequest(); + break; + } + default: + { + throw new IllegalStateException(); + } + } + } + + private void post(Runnable task) { synchronized (tasks) @@ -268,17 +283,71 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect } } - private void endRequest() + private void updateState(State newState) { - // TODO: hasBody is unused, persistent is false in spdy - getEventHandler().headerComplete(false, false); - // TODO: contentLength is unused - getEventHandler().messageComplete(-1); - //TODO: is this the way to go? + logger.debug("State update {} -> {}", state, newState); + state = newState; + } + + private void close(Stream stream) + { + stream.getSession().goAway(); + } + + public void beginRequest(final Headers headers, final boolean endRequest) + { + this.headers = headers.isEmpty() ? null : headers; + post(new Runnable() + { + @Override + public void run() + { + if (!headers.isEmpty()) + updateState(State.REQUEST); + handle(); + if (endRequest) + performEndRequest(); + } + }); + } + + public void headers(Headers headers) + { + this.headers = headers; + post(new Runnable() + { + @Override + public void run() + { + updateState(state == State.INITIAL ? State.REQUEST : State.HEADERS); + handle(); + } + }); + } + + public void endRequest() + { + post(new Runnable() + { + public void run() + { + performEndRequest(); + } + }); + } + + private void performEndRequest() + { + if (state == State.HEADERS) + { + updateState(State.HEADERS_COMPLETE); + handle(); + } + updateState(State.FINAL); handle(); } - private void beginRequest(Headers headers) + private void startRequest(Headers headers) { this.headers = headers; Headers.Header method = headers.get(HTTPSPDYHeader.METHOD.name(getVersion())); @@ -300,7 +369,10 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect Headers.Header schemeHeader = headers.get(HTTPSPDYHeader.SCHEME.name(getVersion())); // if (schemeHeader != null) //TODO: thomas // _request.setScheme(schemeHeader.value()); + } + private void parseHeaders(Headers headers) + { for (Headers.Header header : headers) { String name = header.name(); @@ -400,7 +472,6 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect logger.debug("completeResponse"); getEventHandler().commit(); Response response = getResponse(); - logger.debug("completed"); Headers headers = new Headers(); headers.put(HTTPSPDYHeader.VERSION.name(getVersion()), HttpVersion.HTTP_1_1.asString()); StringBuilder status = new StringBuilder().append(response.getStatus()); @@ -456,7 +527,7 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect // { // ServerHTTPSPDYAsyncConnection pushConnection = // new ServerHTTPSPDYAsyncConnection(getConnector(), getEndPoint(), getServer(), getVersion(), connection, pushStrategy, pushStream); - // pushConnection.beginRequest(requestHeaders, true); + // pushConnection.startRequest(requestHeaders, true); // } // }); } @@ -513,12 +584,4 @@ public class ServerHTTPSPDYAsyncConnectionFactory extends ServerSPDYAsyncConnect return null; } } - - /** - * Needed in order to override generator methods that would generate HTTP, since we must generate SPDY instead. - */ - private class HTTPSPDYGenerator - { - - } } |