diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java index 9b24ea4366..7f3b750ffa 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java @@ -104,6 +104,7 @@ public void channelInactive(ChannelHandlerContext ctx) { } protected void notifyResponse() { + System.out.println("Notify response, " + jerseyResponse); if (jerseyResponse != null) { ClientResponse cr = jerseyResponse; jerseyResponse = null; diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java index 8ab47f6e01..3a9f14cd3e 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyExpectContinueHandler.java @@ -18,20 +18,15 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.http.DefaultFullHttpRequest; import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.LastHttpContent; -import org.glassfish.jersey.client.ClientRequest; import javax.ws.rs.ProcessingException; import java.util.Arrays; import java.util.List; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; public class JerseyExpectContinueHandler extends ChannelInboundHandlerAdapter { @@ -47,8 +42,13 @@ public class JerseyExpectContinueHandler extends ChannelInboundHandlerAdapter { private HttpResponseStatus status = null; + private CountDownLatch latch = null; + + private Boolean chunked = null; + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("Message received: " + msg); if (checkExpectResponse(msg)) { currentState = ExpectationState.AWAITING; } @@ -57,9 +57,9 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception final HttpResponse response = (HttpResponse) msg; status = response.status(); - boolean handshakeDone = HttpUtil.isContentLengthSet(response) || msg instanceof FullHttpMessage; + boolean handshakeDone = Boolean.FALSE.equals(chunked) || msg instanceof FullHttpMessage; currentState = (handshakeDone) ? ExpectationState.IDLE : ExpectationState.FINISHING; - + processLatch(); return; case FINISHING: if (msg instanceof LastHttpContent) { @@ -72,34 +72,15 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } private boolean checkExpectResponse(Object msg) { - if (msg instanceof HttpResponse) { - return expectationStatuses.contains(((HttpResponse) msg).status()); + if (latch != null && msg instanceof HttpResponse) { + return statusesToBeConsidered.contains(((HttpResponse) msg).status()); } return false; } - HttpRequest prepare100ContinueRequest(HttpRequest nettyRequest, - ClientRequest jerseyRequest) - throws InterruptedException, ExecutionException, TimeoutException { - //check for 100-Continue presence/availability - final Expect100ContinueConnectorExtension expect100ContinueExtension - = new Expect100ContinueConnectorExtension(); - final DefaultFullHttpRequest preparedNettyHeadersRequest = - new DefaultFullHttpRequest(nettyRequest.protocolVersion(), nettyRequest.method(), nettyRequest.uri()); - preparedNettyHeadersRequest.headers().setAll(nettyRequest.headers()); - - if (!preparedNettyHeadersRequest.headers().contains(HttpHeaderNames.HOST)) { - preparedNettyHeadersRequest.headers().add(HttpHeaderNames.HOST, jerseyRequest.getUri().getHost()); - } - - //If Expect:100-continue feature is enabled and client supports it, the nettyRequestHeaders will be - //enriched with the 'Expect:100-continue' header. - expect100ContinueExtension.invoke(jerseyRequest, preparedNettyHeadersRequest); - - return preparedNettyHeadersRequest; - } - void processExpectationStatus() + boolean processExpectationStatus() throws TimeoutException { + System.out.println(status); if (status == null) { throw new TimeoutException(); // continue without expectations } @@ -111,13 +92,39 @@ void processExpectationStatus() throw new ProcessingException(LocalizationMessages .REQUEST_ENTITY_TOO_LARGE(), null); } + + if (HttpResponseStatus.METHOD_NOT_ALLOWED.equals(status)) { + throw new TimeoutException(HttpResponseStatus + .METHOD_NOT_ALLOWED.reasonPhrase()); // Re-send request without expectations + } if (HttpResponseStatus.EXPECTATION_FAILED.equals(status)) { - throw new TimeoutException(); // continue without expectations + throw new TimeoutException(HttpResponseStatus + .EXPECTATION_FAILED.reasonPhrase()); // continue without expectations } if (!HttpResponseStatus.CONTINUE.equals(status)) { throw new ProcessingException(LocalizationMessages .UNEXPECTED_VALUE_FOR_EXPECT_100_CONTINUE_STATUSES(status.code()), null); } + + return true; + } + + void attachCountDownLatch(CountDownLatch latch) { + this.latch = latch; + } + + private void processLatch() { + if (latch != null) { + latch.countDown(); + } + } + + void removeLatch() { + latch = null; + } + + void setChunked(Boolean chunk) { + this.chunked = chunk; } private enum ExpectationState { diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java index fe3ad7da59..f8e57ac077 100644 --- a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java @@ -32,7 +32,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -448,23 +447,6 @@ public void operationComplete(io.netty.util.concurrent.Future futu final CountDownLatch headersSet = new CountDownLatch(1); final CountDownLatch contentLengthSet = new CountDownLatch(1); - try { - final HttpRequest expect100ContinueRequest = - expect100ContinueHandler.prepare100ContinueRequest(nettyRequest, jerseyRequest); - if (HttpUtil.is100ContinueExpected(expect100ContinueRequest)) { - //send expect request, sync and wait till either response or timeout received - boolean completed = ch.writeAndFlush(expect100ContinueRequest) - .sync().await(expect100ContinueTimeout, TimeUnit.MILLISECONDS); - if (completed) { - expect100ContinueHandler.processExpectationStatus(); - } //if not we just continue without expectations - } - } catch (ExecutionException e) { - responseDone.completeExceptionally(e); - } catch (TimeoutException e) { - //Expect:100-continue allows timeouts by the spec - } - jerseyRequest.setStreamProvider(new OutboundMessageContext.StreamProvider() { @Override public OutputStream getOutputStream(int contentLength) throws IOException { @@ -488,24 +470,60 @@ public void run() { if (entityWriter.getType() == NettyEntityWriter.Type.DELAYED) { nettyRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH, entityWriter.getLength()); contentLengthSet.countDown(); + System.out.println("Content-length triggered"); } } catch (IOException e) { + e.printStackTrace(); responseDone.completeExceptionally(e); } } }); + + new Expect100ContinueConnectorExtension().invoke(jerseyRequest, nettyRequest); + headersSet.await(); - entityWriter.writeAndFlush(nettyRequest); + if (HttpUtil.is100ContinueExpected(nettyRequest)) { + System.out.println(nettyRequest); + final CountDownLatch expect100ContinueLatch = new CountDownLatch(1); + expect100ContinueHandler.attachCountDownLatch(expect100ContinueLatch); + expect100ContinueHandler.setChunked(NettyEntityWriter.Type.CHUNKED.equals(entityWriter.getType())); + //send expect request, sync and wait till either response or timeout received + ch.writeAndFlush(nettyRequest) + .sync().await(expect100ContinueTimeout, TimeUnit.MILLISECONDS); + expect100ContinueLatch.await(); + try { + System.out.println("Processing expectation status"); + expect100ContinueHandler.processExpectationStatus(); + } catch (TimeoutException e) { + //Expect:100-continue allows timeouts by the spec + //so, send request directly without Expect header. + HttpUtil.set100ContinueExpected(nettyRequest, false); + expect100ContinueHandler.removeLatch(); + entityWriter.writeAndFlush(nettyRequest); + System.out.println("request re-sent w/o expectations: " + nettyRequest); + } finally { + //restore request and handler to the original state. + HttpUtil.set100ContinueExpected(nettyRequest, false); + expect100ContinueHandler.removeLatch(); + expect100ContinueHandler.setChunked(null); + } + } else { + entityWriter.writeAndFlush(nettyRequest); + } + + System.out.println("entity processing"); if (HttpUtil.isTransferEncodingChunked(nettyRequest)) { entityWriter.write(new HttpChunkedInput(entityWriter.getChunkedInput())); } else { + System.out.println("write it"); entityWriter.write(entityWriter.getChunkedInput()); } if (entityWriter.getType() == NettyEntityWriter.Type.DELAYED) { + System.out.println("count down latch for content length " + contentLengthSet); contentLengthSet.await(); } entityWriter.flush(); diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/nettyconnector/Expect100ContinueTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/nettyconnector/Expect100ContinueTest.java index bcb350d16d..b8084f9405 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/nettyconnector/Expect100ContinueTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/nettyconnector/Expect100ContinueTest.java @@ -16,7 +16,6 @@ package org.glassfish.jersey.tests.e2e.client.nettyconnector; -import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.glassfish.jersey.client.ClientConfig; @@ -26,10 +25,12 @@ import org.glassfish.jersey.netty.connector.NettyClientProperties; import org.glassfish.jersey.netty.connector.NettyConnectorProvider; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.net.ServerSocketFactory; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.ProcessingException; @@ -38,8 +39,24 @@ import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -58,19 +75,20 @@ public class Expect100ContinueTest /*extends JerseyTest*/ { private static final String RESOURCE_PATH_METHOD_NOT_SUPPORTED = "fail405"; private static final String ENTITY_STRING = "1234567890123456789012345678901234567890123456789012" - + "3456789012345678901234567890"; + + "3456789012345678901234567890"; private static final Integer portNumber = 9997; - private static Server server; + private static TestSocketServer server; + @BeforeAll public static void startExpect100ContinueTestServer() { - server = new Server(portNumber); - server.setHandler(new Expect100ContinueTestHandler()); + try { - server.start(); + server = new TestSocketServer(portNumber); + server.runServer(); } catch (Exception e) { - + e.printStackTrace(); } } @@ -79,10 +97,12 @@ public static void stopExpect100ContinueTestServer() { try { server.stop(); } catch (Exception e) { + e.printStackTrace(); } } private static Client client; + @BeforeEach public void beforeEach() { final ClientConfig config = new ClientConfig(); @@ -104,137 +124,322 @@ protected void configureClient(ClientConfig config) { @Test public void testExpect100Continue() { - final Response response = target(RESOURCE_PATH).request().post(Entity.text(ENTITY_STRING)); - assertEquals(200, response.getStatus(), "Expected 200"); //no Expect header sent - response OK + System.out.println("NO EXPECT"); + final Response response = target(RESOURCE_PATH).request().post(Entity.text(ENTITY_STRING)); + assertEquals(200, response.getStatus(), "Expected 200"); //no Expect header sent - response OK } @Test public void testExpect100ContinueChunked() { - final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.basic()) - .property(ClientProperties.REQUEST_ENTITY_PROCESSING, - RequestEntityProcessing.CHUNKED).request().post(Entity.text(ENTITY_STRING)); - assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response + System.out.println("DEFAULT EXPECT CHUNKED"); + final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.basic()) + .property(ClientProperties.REQUEST_ENTITY_PROCESSING, + RequestEntityProcessing.CHUNKED) + .request().post(Entity.text(ENTITY_STRING)); + System.out.println(response); + assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response + System.out.println("PASSED"); } @Test public void testExpect100ContinueBuffered() { - final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.basic()) - .property(ClientProperties.REQUEST_ENTITY_PROCESSING, - RequestEntityProcessing.BUFFERED).request().header(HttpHeaders.CONTENT_LENGTH, 67000L) - .post(Entity.text(ENTITY_STRING)); - assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response + System.out.println("BUFFERED EXPECT"); + final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.basic()) + .property(ClientProperties.REQUEST_ENTITY_PROCESSING, + RequestEntityProcessing.BUFFERED).request().header(HttpHeaders.CONTENT_LENGTH, 67000L) + .post(Entity.text(generateStringByContentLength(67000))); + assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response } @Test public void testExpect100ContinueCustomLength() { - final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L)) - .request().header(HttpHeaders.CONTENT_LENGTH, Integer.MAX_VALUE) - .post(Entity.text(ENTITY_STRING)); - assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response + final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L)) + .request().header(HttpHeaders.CONTENT_LENGTH, 200) + .post(Entity.text(generateStringByContentLength(200))); + assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response } @Test public void testExpect100ContinueCustomLengthWrong() { - final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L)) - .request().header(HttpHeaders.CONTENT_LENGTH, 99L) - .post(Entity.text(ENTITY_STRING)); - assertEquals(200, response.getStatus(), "Expected 200"); //Expect header NOT sent - low request size + final Response response = target(RESOURCE_PATH).register(Expect100ContinueFeature.withCustomThreshold(100L)) + .request().header(HttpHeaders.CONTENT_LENGTH, 99L) + .post(Entity.text(generateStringByContentLength(99))); + assertEquals(200, response.getStatus(), "Expected 200"); //Expect header NOT sent - low request size } @Test public void testExpect100ContinueCustomLengthProperty() { - final Response response = target(RESOURCE_PATH) - .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 555L) - .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) - .register(Expect100ContinueFeature.withCustomThreshold(555L)) - .request().header(HttpHeaders.CONTENT_LENGTH, 666L) - .post(Entity.text(ENTITY_STRING)); - assertNotNull(response.getStatus()); //Expect header sent - No Content response + System.out.println("EXPECT 100 CONTINUE CUSTOM PROPERTY"); + final Response response = target(RESOURCE_PATH) + .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 555L) + .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) + .register(Expect100ContinueFeature.withCustomThreshold(555L)) + .request().header(HttpHeaders.CONTENT_LENGTH, 666L) + .post(Entity.text(generateStringByContentLength(666))); + assertNotNull(response.getStatus()); //Expect header sent - No Content response } @Test public void testExpect100ContinueRegisterViaCustomProperty() { - final Response response = target(RESOURCE_PATH) - .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) - .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) - .request().header(HttpHeaders.CONTENT_LENGTH, 44L) - .post(Entity.text(ENTITY_STRING)); - assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response + final Response response = target(RESOURCE_PATH) + .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) + .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) + .request().header(HttpHeaders.CONTENT_LENGTH, 44L) + .post(Entity.text(generateStringByContentLength(44))); + assertEquals(204, response.getStatus(), "Expected 204"); //Expect header sent - No Content response } @Test public void testExpect100ContinueNotSupported() { - final Response response = target(RESOURCE_PATH_NOT_SUPPORTED) - .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) - .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) - .request().header(HttpHeaders.CONTENT_LENGTH, 44L) - .post(Entity.text(ENTITY_STRING)); - assertEquals(417, response.getStatus(), "Expected 417"); //Expectations not supported + final Response response = target(RESOURCE_PATH_NOT_SUPPORTED) + .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) + .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) + .request().header(HttpHeaders.CONTENT_LENGTH, 44L) + .post(Entity.text(generateStringByContentLength(44))); + assertEquals(417, response.getStatus(), "Expected 417"); //Expectations not supported } @Test public void testExpect100ContinueUnauthorized() { - assertThrows(ProcessingException.class, () -> target(RESOURCE_PATH_UNAUTHORIZED) - .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) - .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) - .property(NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT, 10000) - .request().header(HttpHeaders.CONTENT_LENGTH, 44L) - .post(Entity.text(ENTITY_STRING))); + assertThrows(ProcessingException.class, () -> target(RESOURCE_PATH_UNAUTHORIZED) + .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) + .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) + .property(NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT, 10000) + .request().header(HttpHeaders.CONTENT_LENGTH, 44L) + .post(Entity.text(generateStringByContentLength(44)))); } @Test public void testExpect100ContinuePayloadTooLarge() { assertThrows(ProcessingException.class, () -> target(RESOURCE_PATH_PAYLOAD_TOO_LARGE) - .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) - .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) - .property(NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT, 10000) - .request().header(HttpHeaders.CONTENT_LENGTH, 44L) - .post(Entity.text(ENTITY_STRING))); + .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) + .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) + .property(NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT, 10000) + .request().header(HttpHeaders.CONTENT_LENGTH, 44L) + .post(Entity.text(generateStringByContentLength(44)))); } @Test public void testExpect100ContinueMethodNotSupported() { - assertThrows(ProcessingException.class, () -> target(RESOURCE_PATH_METHOD_NOT_SUPPORTED) - .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) - .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) - .property(NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT, 10000) - .request().header(HttpHeaders.CONTENT_LENGTH, 44L) - .post(Entity.text(ENTITY_STRING))); - } - - static class Expect100ContinueTestHandler extends AbstractHandler { - @Override - public void handle(String target, - Request baseRequest, - HttpServletRequest request, - HttpServletResponse response) throws IOException { - boolean expected = request.getHeader("Expect") != null; - boolean failed = false; - if (target.equals("/" + RESOURCE_PATH_NOT_SUPPORTED)) { - response.sendError(417); - failed = true; + final Response response = target(RESOURCE_PATH_METHOD_NOT_SUPPORTED) + .property(ClientProperties.EXPECT_100_CONTINUE_THRESHOLD_SIZE, 43L) + .property(ClientProperties.EXPECT_100_CONTINUE, Boolean.TRUE) + .property(NettyClientProperties.EXPECT_100_CONTINUE_TIMEOUT, 10000) + .request().header(HttpHeaders.CONTENT_LENGTH, 44L) + .post(Entity.text(generateStringByContentLength(44))); + + assertEquals(200, response.getStatus(), + "This should re-send request without expect and obtain the 200 response code"); + } + + private String generateStringByContentLength(int length) { + final byte[] array = new byte[length]; + new Random().nextBytes(array); + return new String(array); + } + + private static final class TestSocketServer { + + private static final String NO_CONTENT_HEADER = "HTTP/1.1 204 No Content"; + private static final String OK_HEADER = "HTTP/1.1 200 OK"; + private static final String UNAUTHORIZED_HEADER = "HTTP/1.1 401 Unauthorized"; + private static final String NOT_SUPPORTED_HEADER = "HTTP/1.1 405 Method Not Allowed"; + + private boolean unauthorized = false; + private boolean not_supported = false; + + private ServerSocket server; + + private final ExecutorService executorService = Executors.newCachedThreadPool(); + + private boolean stopped = false; + + public TestSocketServer(int port) throws IOException { + final ServerSocketFactory socketFactory = ServerSocketFactory.getDefault(); + server = socketFactory.createServerSocket(port); + } + + void stop() { + stopped = true; + try { + server.close(); + executorService.shutdown(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + void runServer() throws Exception { + + executorService.execute(() -> { + try { + while (!stopped) { + final Socket socket = server.accept(); + executorService.submit(() -> processRequest(socket)); + } + } catch (IOException e) { + //do nothing + } + }); + } + + private void processRequest(final Socket request) { + int count = 0; + try { + System.out.println("Request received: " + request); + final BufferedReader reader + = new BufferedReader(new InputStreamReader(request.getInputStream())); + final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(request.getOutputStream())); + Map headers; + boolean failed; + + do { + System.out.println("====Entering loop processing======"); + headers = mapHeaders(reader); + printHeaders(headers); + //printRequest(reader); + + + + //simply print the data for debugging purposes + System.out.println(count + ", " + + (headers.containsKey("expect") && headers.containsValue("100-continue"))); + + + + failed = processExpect100Continue(headers, writer); + + System.out.println("====If " + failed + ", then loop again======"); + } while (failed); + + String http_header = OK_HEADER; +// System.out.println("Sending 204 response"); +// System.out.println(headers.get("content-length")); + System.out.println("Reading body"); + readBody(reader, headers); + writer.write(http_header); + + addHeader("transfer-encoding", headers, writer); + addHeader("content-type", headers, writer); +// writer.write("Connection: Close\r\n"); + + + writer.write("Content-Length: 0"); + + System.out.println("Content received"); + + closeConnection(writer, reader, request); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addHeader(String key, Map headers, BufferedWriter writer) throws IOException { + if (headers.containsKey(key)) { + writer.newLine(); + writer.write(key + ":" + headers.get(key)); } - if (target.equals("/" + RESOURCE_PATH_UNAUTHORIZED)) { - response.sendError(401); - failed = true; + } + + private void closeConnection(BufferedWriter writer, BufferedReader reader, Socket request) throws IOException { + writer.newLine(); + writer.flush(); + writer.close(); + reader.close(); + request.close(); + } + + private boolean processExpect100Continue(Map headers, BufferedWriter writer) throws IOException { + String http_header = OK_HEADER; + boolean failed = false; + if (headers.containsKey("expect") && headers.containsValue("100-continue")) { +// System.out.println("Sending 100-continue response"); + + if (unauthorized) { + http_header = UNAUTHORIZED_HEADER; + unauthorized = false; + failed = true; + } + + if (not_supported) { + http_header = NOT_SUPPORTED_HEADER; + not_supported = false; + failed = true; + } + + writer.write(http_header); + writer.newLine(); + writer.newLine(); + writer.flush(); + headers.remove("expect"); + + http_header = NO_CONTENT_HEADER; + +// addHeader("content-length", headers, writer); +// addHeader("transfer-encoding", headers, writer); + + System.out.println("Unauthorized, not supported flags: " + unauthorized + ", " + not_supported); + + } - if (target.equals("/" + RESOURCE_PATH_PAYLOAD_TOO_LARGE)) { - response.sendError(413); - failed = true; + + return failed; + + } + + private Map mapHeaders(BufferedReader reader) throws IOException { + String line; + final Map headers = new HashMap<>(); + int i = 0; + while (i++ < 6) { + line = reader.readLine(); + if (line.contains(RESOURCE_PATH_UNAUTHORIZED)) { + unauthorized = true; + } + + if (line.contains(RESOURCE_PATH_METHOD_NOT_SUPPORTED)) { + not_supported = true; + } + int pos = line.indexOf(':'); + if (pos > -1) { + headers.put( + line.substring(0, pos).toLowerCase(Locale.ROOT), + line.substring(pos + 2).toLowerCase(Locale.ROOT)); + } } - if (target.equals("/" + RESOURCE_PATH_METHOD_NOT_SUPPORTED)) { - response.sendError(405); - failed = true; + return headers; + } + + private void readHeader(BufferedReader reader) throws IOException { + for (int i = 0; i < 6; i++) { +// reader.readLine(); + System.out.println(reader.readLine()); } - if (expected && !failed) { - System.out.println("Expect:100-continue found, sending response header"); - response.setStatus(204); + } + + private void readBody(BufferedReader reader, Map headers) throws IOException { + System.out.println("Reading body from reader"); + if (headers.containsKey("content-length")) { + int contentLength = Integer.valueOf(headers.get("content-length")); + final char[] content = new char[contentLength]; + reader.read(content); +// System.out.println(content); + } else if (headers.containsKey("transfer-encoding")) { + String line = ""; + while (!line.equals("0")) { + line = reader.readLine(); + System.out.println(line); + } + System.out.println(reader.readLine()); } - response.getWriter().println(); - response.flushBuffer(); - baseRequest.setHandled(true); + } - request.getReader().lines().forEach(System.out::println); + private void printHeaders(Map headers) { + for (Map.Entry entry : headers.entrySet()) { + System.out.println(entry.getKey() + ", " + entry.getValue()); + } } } } \ No newline at end of file