@Override public void exchangeTerminated(HttpExchange exchange, Result result) { super.exchangeTerminated(exchange, result); Response response = result.getResponse(); HttpFields responseHeaders = response.getHeaders(); String closeReason = null; if (result.isFailed()) closeReason = "failure"; else if (receiver.isShutdown()) closeReason = "server close"; if (closeReason == null) { if (response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0) { // HTTP 1.0 must close the connection unless it has // an explicit keep alive or it's a CONNECT method. boolean keepAlive = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); boolean connect = HttpMethod.CONNECT.is(exchange.getRequest().getMethod()); if (!keepAlive && !connect) closeReason = "http/1.0"; } else { // HTTP 1.1 or greater closes only if it has an explicit close. if (responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())) closeReason = "http/1.1"; } } if (closeReason != null) { if (LOG.isDebugEnabled()) LOG.debug("Closing, reason: {} - {}", closeReason, connection); connection.close(); } else { if (response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101) connection.remove(); else release(); } }
protected boolean success() { HttpExchange exchange = connection.getExchange(); if (exchange == null) return false; AtomicMarkableReference<Result> completion = exchange.responseComplete(null); if (!completion.isMarked()) return false; parser.reset(); decoder = null; if (!updateState(State.RECEIVE, State.IDLE)) throw new IllegalStateException(); exchange.terminateResponse(); HttpResponse response = exchange.getResponse(); List<Response.ResponseListener> listeners = exchange.getConversation().getResponseListeners(); ResponseNotifier notifier = connection.getDestination().getResponseNotifier(); notifier.notifySuccess(listeners, response); LOG.debug("Received {}", response); Result result = completion.getReference(); if (result != null) { connection.complete(exchange, !result.isFailed()); notifier.notifyComplete(listeners, result); } return true; }
@Override public Result exchangeTerminating(HttpExchange exchange, Result result) { if (result.isFailed()) return result; HttpResponse response = exchange.getResponse(); if ((response.getVersion() == HttpVersion.HTTP_1_1) && (response.getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)) { String connection = response.getHeaders().get(HttpHeader.CONNECTION); if ((connection == null) || !connection.toLowerCase(Locale.US).contains("upgrade")) { return new Result( result, new HttpResponseException( "101 Switching Protocols without Connection: Upgrade not supported", response)); } // Upgrade Response HttpRequest request = exchange.getRequest(); if (request instanceof HttpConnectionUpgrader) { HttpConnectionUpgrader listener = (HttpConnectionUpgrader) request; try { listener.upgrade(response, getHttpConnection()); } catch (Throwable x) { return new Result(result, x); } } } return result; }
@Test public void testProxyRequestFailureInTheMiddleOfProxyingSmallContent() throws Exception { final long proxyTimeout = 1000; Map<String, String> proxyParams = new HashMap<>(); proxyParams.put("timeout", String.valueOf(proxyTimeout)); prepareProxy(proxyParams); final CountDownLatch chunk1Latch = new CountDownLatch(1); final int chunk1 = 'q'; final int chunk2 = 'w'; prepareServer( new HttpServlet() { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletOutputStream output = response.getOutputStream(); output.write(chunk1); response.flushBuffer(); // Wait for the client to receive this chunk. await(chunk1Latch, 5000); // Send second chunk, must not be received by proxy. output.write(chunk2); } private boolean await(CountDownLatch latch, long ms) throws IOException { try { return latch.await(ms, TimeUnit.MILLISECONDS); } catch (InterruptedException x) { throw new InterruptedIOException(); } } }); HttpClient client = prepareClient(); InputStreamResponseListener listener = new InputStreamResponseListener(); int port = serverConnector.getLocalPort(); client.newRequest("localhost", port).send(listener); // Make the proxy request fail; given the small content, the // proxy-to-client response is not committed yet so it will be reset. TimeUnit.MILLISECONDS.sleep(2 * proxyTimeout); Response response = listener.get(5, TimeUnit.SECONDS); Assert.assertEquals(504, response.getStatus()); // Make sure there is no content, as the proxy-to-client response has been reset. InputStream input = listener.getInputStream(); Assert.assertEquals(-1, input.read()); chunk1Latch.countDown(); // Result succeeds because a 504 is a valid HTTP response. Result result = listener.await(5, TimeUnit.SECONDS); Assert.assertTrue(result.isSucceeded()); // Make sure the proxy does not receive chunk2. Assert.assertEquals(-1, input.read()); HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP) client.getDestination("http", "localhost", port); Assert.assertEquals(0, destination.getConnectionPool().getIdleConnections().size()); }