All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.swisspush.gateleen.routing.DeferCloseHttpClient Maven / Gradle / Ivy

There is a newer version: 2.1.12
Show newest version
package org.swisspush.gateleen.routing;

import io.vertx.core.*;
import io.vertx.core.http.*;
import io.vertx.core.net.SSLOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;


/**
 * Decorates an {@link HttpClient} to only effectively close the client
 * if there are no more requests in progress.
 * 

* HINT: We for now only fix the issue in the exact call we know to misbehave * in our concrete scenario. Feel free to implement the (few...) remaining * methods. */ public class DeferCloseHttpClient implements HttpClient { private final int CLOSE_ANYWAY_AFTER_MS = 86_400_000; // <- TODO: Find a good value. private static final Logger logger = LoggerFactory.getLogger(DeferCloseHttpClient.class); private final Vertx vertx; private final HttpClient delegate; private int countOfRequestsInProgress = 0; private boolean doCloseWhenDone = false; /** * See {@link DeferCloseHttpClient}. */ public DeferCloseHttpClient(Vertx vertx, HttpClient delegate) { this.vertx = vertx; this.delegate = delegate; } @Override public void request(HttpMethod method, int port, String host, String requestURI, Handler> handler) { logger.debug("({}:{}).request({}, \"{}\")", host, port, method, requestURI); countOfRequestsInProgress += 1; logger.debug("Pending request count: {}", countOfRequestsInProgress); delegate.request(method, port, host, requestURI).onComplete(asyncRequestResult -> { if (asyncRequestResult.failed()) { logger.debug("({}:{}).request({}, \"{}\") failed in request() with {}", host, port, method, requestURI, asyncRequestResult.cause()); // do the same as further down onEndOfRequestResponseCycle(); return; } HttpClientRequest request = asyncRequestResult.result(); request.response(asyncResponseResult -> { if (asyncResponseResult.failed()) { logger.debug("({}:{}).request({}, \"{}\") failed in response() with {}", host, port, method, requestURI, asyncResponseResult.cause()); // Does not make sense to install any handlers. Just make sure we decrement // our counter then pass-through the exception. onEndOfRequestResponseCycle(); return; } HttpClientResponse upstreamRsp = asyncResponseResult.result(); // Delegate to the same method on the delegate. But install our own handler which // allows us to intercept the response. logger.debug("onUpstreamRsp(code={})", upstreamRsp.statusCode()); // 1st we have to pass-through the response so our caller is able to install its handlers. // We also need to ensure that our reference counter stays accurate. Badly vertx // may call BOTH of our handlers. And in this scenario we MUST NOT decrement // twice. So we additionally track this too. final AtomicBoolean needToDecrementCounter = new AtomicBoolean(true); // Then (after client installed its handlers), we now can intercept those by // replacing them with our own handlers. // To do this, we 1st backup the original handler (so we can delegate to it later). Handler originalEndHandler = getEndHandler(upstreamRsp); upstreamRsp.endHandler(event -> { logger.debug("upstreamRsp.endHandler()"); if (needToDecrementCounter.getAndSet(false)) { onEndOfRequestResponseCycle(); } // Call the original handler independent of the above condition to not change // behaviour of the impl we are decorating. callHandlerIfExists(originalEndHandler, event); }); // We also need to intercept exception handler to decrement our counter in case // of erroneous-end scenario. Basically same idea as above. Handler originalExceptionHandler = getExceptionHandler(upstreamRsp); upstreamRsp.exceptionHandler(event -> { logger.debug("upstreamRsp.exceptionHandler({})", event.toString()); if (needToDecrementCounter.getAndSet(false)) { onEndOfRequestResponseCycle(); } callHandlerIfExists(originalExceptionHandler, event); }); }); }).onComplete(handler); } private void onEndOfRequestResponseCycle() { countOfRequestsInProgress -= 1; logger.debug("Pending request count: {}", countOfRequestsInProgress); if (countOfRequestsInProgress == 0 && doCloseWhenDone) { logger.debug("No pending request right now. And someone called 'close()' earlier. So close now."); doCloseWhenDone = false; try { delegate.close(); } catch (Exception e) { logger.warn("delegate.close() failed", e); } } } @Override public Future close() { if (countOfRequestsInProgress > 0) { logger.debug("Do NOT close right now. But close as soon there are no more pending requests (pending={})", countOfRequestsInProgress); doCloseWhenDone = true; // Still use a timer. Because who knows. vertx.setTimer(CLOSE_ANYWAY_AFTER_MS, timerId -> { if (doCloseWhenDone) { logger.warn("RequestResponse cycle still running after {} ms. Will close now to prevent resource leaks.", CLOSE_ANYWAY_AFTER_MS); doCloseWhenDone = false; delegate.close(); } }); return Future.succeededFuture(); } logger.debug("Client idle. Close right now"); delegate.close(); return Future.succeededFuture(); } /////////////////////////////////////////////////////////////////////////////// // Some helpers so we are able to do our work. /////////////////////////////////////////////////////////////////////////////// private void callHandlerIfExists(Handler maybeNull, T event) { if (maybeNull == null) { return; // No handler? Nothing we could call. } maybeNull.handle(event); } private Handler getEndHandler(HttpClientResponse rsp) { return getPrivateField(rsp, "endHandler"); } private Handler getExceptionHandler(HttpClientResponse rsp) { return getPrivateField(rsp, "exceptionHandler"); } private T getPrivateField(HttpClientResponse rsp, String name) { try { Field eventHandlerField = rsp.getClass().getDeclaredField("eventHandler"); eventHandlerField.setAccessible(true); Object eventHanlderObj = eventHandlerField.get(rsp); if (eventHanlderObj == null) return null; Field handlerField = eventHanlderObj.getClass().getDeclaredField(name); handlerField.setAccessible(true); return (T) handlerField.get(eventHanlderObj); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } /////////////////////////////////////////////////////////////////////////////// // Below are only the remaining methods which all just delegate. /////////////////////////////////////////////////////////////////////////////// @Override public Future request(RequestOptions options) { return delegate.request(options); } @Override public void request(RequestOptions options, Handler> handler) { delegate.request(options, handler); } @Override public Future request(HttpMethod method, int port, String host, String requestURI) { return delegate.request(method, port, host, requestURI); } @Override public void request(HttpMethod method, String host, String requestURI, Handler> handler) { delegate.request(method, host, requestURI, handler); } @Override public Future request(HttpMethod method, String host, String requestURI) { return delegate.request(method, host, requestURI); } @Override public void request(HttpMethod method, String requestURI, Handler> handler) { delegate.request(method, requestURI, handler); } @Override public Future request(HttpMethod method, String requestURI) { return delegate.request(method, requestURI); } @Override public void webSocket(int port, String host, String requestURI, Handler> handler) { delegate.webSocket(port, host, requestURI, handler); } @Override public Future webSocket(int port, String host, String requestURI) { return delegate.webSocket(port, host, requestURI); } @Override public void webSocket(String host, String requestURI, Handler> handler) { delegate.webSocket(host, requestURI, handler); } @Override public Future webSocket(String host, String requestURI) { return delegate.webSocket(host, requestURI); } @Override public void webSocket(String requestURI, Handler> handler) { delegate.webSocket(requestURI, handler); } @Override public Future webSocket(String requestURI) { return delegate.webSocket(requestURI); } @Override public void webSocket(WebSocketConnectOptions options, Handler> handler) { delegate.webSocket(options, handler); } @Override public Future webSocket(WebSocketConnectOptions options) { return delegate.webSocket(options); } @Override public void webSocketAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols, Handler> handler) { delegate.webSocketAbs(url, headers, version, subProtocols, handler); } @Override public Future webSocketAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols) { return delegate.webSocketAbs(url, headers, version, subProtocols); } @Override public Future updateSSLOptions(SSLOptions options, boolean force) { return delegate.updateSSLOptions(options, force); } @Override public HttpClient connectionHandler(Handler handler) { return delegate.connectionHandler(handler); } @Override public HttpClient redirectHandler(Function> handler) { return delegate.redirectHandler(handler); } @Override public Function> redirectHandler() { return delegate.redirectHandler(); } @Override public void close(Handler> handler) { delegate.close(handler); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy