From 5abbe05c668ef5dff52caf92333ecb1bac93a3ec Mon Sep 17 00:00:00 2001 From: Lautaro Cozzani Date: Mon, 8 Jun 2015 18:56:22 -0300 Subject: [PATCH] #306: Modify BkBasic for persistent conections according to HTTP 1.1 --- src/main/java/org/takes/http/BkBasic.java | 46 +++++++++-- src/test/java/org/takes/http/BkBasicTest.java | 76 ++++++++++++++++++- 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/takes/http/BkBasic.java b/src/main/java/org/takes/http/BkBasic.java index aac8d9a69..b64d97e1c 100644 --- a/src/main/java/org/takes/http/BkBasic.java +++ b/src/main/java/org/takes/http/BkBasic.java @@ -32,15 +32,18 @@ import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.Socket; +import java.util.Iterator; import lombok.EqualsAndHashCode; import org.takes.HttpException; import org.takes.Request; import org.takes.Response; import org.takes.Take; +import org.takes.rq.RqHeaders; import org.takes.rq.RqLive; import org.takes.rq.RqWithHeaders; import org.takes.rs.RsPrint; import org.takes.rs.RsText; +import org.takes.rs.RsWithHeader; import org.takes.rs.RsWithStatus; /** @@ -57,6 +60,16 @@ @EqualsAndHashCode(of = "take") public final class BkBasic implements Back { + /** + * Keep alive header key. + */ + private static final String CONNECTION = "Connection"; + + /** + * Keep alive header value. + */ + private static final String KEEP_ALIVE = "Keep-Alive"; + /** * Take. */ @@ -73,16 +86,28 @@ public BkBasic(final Take tks) { @Override public void accept(final Socket socket) throws IOException { final InputStream input = socket.getInputStream(); + final RqLive req = new RqLive(input); + boolean keep = false; + final Iterator values = new RqHeaders.Base(req) + .header(CONNECTION).iterator(); + if (values.hasNext()) { + do { + keep = KEEP_ALIVE.equals(values.next()); + } while (!keep && values.hasNext()); + } try { this.print( BkBasic.addSocketHeaders( - new RqLive(input), + req, socket ), - new BufferedOutputStream(socket.getOutputStream()) + new BufferedOutputStream(socket.getOutputStream()), + keep ); } finally { - input.close(); + if (!keep) { + input.close(); + } } } @@ -90,13 +115,24 @@ public void accept(final Socket socket) throws IOException { * Print response to output stream, safely. * @param req Request * @param output Output + * @param keep Keep connection open * @throws IOException If fails */ @SuppressWarnings("PMD.AvoidCatchingThrowable") - private void print(final Request req, final OutputStream output) + private void print( + final Request req, final OutputStream output, final boolean keep + ) throws IOException { try { - new RsPrint(this.take.act(req)).print(output); + final Response res; + if (keep) { + res = new RsWithHeader( + this.take.act(req), CONNECTION, KEEP_ALIVE + ); + } else { + res = this.take.act(req); + } + new RsPrint(res).print(output); } catch (final HttpException ex) { new RsPrint(BkBasic.failure(ex, ex.code())).print(output); // @checkstyle IllegalCatchCheck (7 lines) diff --git a/src/test/java/org/takes/http/BkBasicTest.java b/src/test/java/org/takes/http/BkBasicTest.java index 6ccb93f13..73322b6f7 100644 --- a/src/test/java/org/takes/http/BkBasicTest.java +++ b/src/test/java/org/takes/http/BkBasicTest.java @@ -50,6 +50,13 @@ * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) */ public final class BkBasicTest { + + /** + * Keep alive header value. + */ + private static final String KEEP_ALIVE = + "Connection: Keep-Alive"; + /** * BkBasic can handle socket data. * @throws IOException If some problem inside @@ -59,7 +66,7 @@ public void handlesSocket() throws IOException { final Socket socket = Mockito.mock(Socket.class); Mockito.when(socket.getInputStream()).thenReturn( new ByteArrayInputStream( - Joiner.on("\r\n").join( + this.joiner().join( "GET / HTTP/1.1", "Host:localhost", "Content-Length: 2", @@ -85,6 +92,64 @@ public void handlesSocket() throws IOException { ); } + /** + * BkBasic supports HTTP persistent connections. + * @throws IOException If some problem inside + */ + @Test + public void persistentConnection() throws IOException { + final Socket socket = Mockito.mock(Socket.class); + final ByteArrayInputStream input = Mockito.spy( + new ByteArrayInputStream( + this.joiner().join( + "GET /keep HTTP/1.1", + "Host:localhost2", + "Content-Length: 5", + KEEP_ALIVE, + "", + "hello" + ).getBytes() + ) + ); + Mockito.when(socket.getInputStream()).thenReturn(input); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Mockito.when(socket.getOutputStream()).thenReturn(baos); + new BkBasic(new TkText("Hello!")).accept(socket); + Mockito.verify(input, Mockito.never()).close(); + MatcherAssert.assertThat( + baos.toString(), + Matchers.containsString(KEEP_ALIVE) + ); + } + + /** + * BkBasic not supports HTTP persistent connections. + * @throws IOException If some problem inside + */ + @Test + public void notPersistentConnection() throws IOException { + final Socket socket = Mockito.mock(Socket.class); + final ByteArrayInputStream input = Mockito.spy( + new ByteArrayInputStream( + this.joiner().join( + "GET /close HTTP/1.1", + "Host:localhost3", + "Content-Length: 6", + "", + "hi all" + ).getBytes() + ) + ); + Mockito.when(socket.getInputStream()).thenReturn(input); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Mockito.when(socket.getOutputStream()).thenReturn(baos); + new BkBasic(new TkText("Hello")).accept(socket); + Mockito.verify(input, Mockito.times(1)).close(); + MatcherAssert.assertThat( + baos.toString(), + Matchers.not(Matchers.containsString(KEEP_ALIVE)) + ); + } /** * BkBasic can return HTTP status 404 when accessing invalid URL. * @throws IOException if any I/O error occurs. @@ -108,4 +173,13 @@ public void exec(final URI home) throws IOException { } ); } + + /** + * Create a joiner for a header. + * @return Joiner + */ + private Joiner joiner() { + return Joiner.on("\r\n"); + } + }