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

org.apache.http.impl.nio.client.AbstractClientExchangeHandler Maven / Gradle / Ivy

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * .
 *
 */
package org.apache.http.impl.nio.client;

import java.io.IOException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.logging.Log;
import org.apache.http.ConnectionClosedException;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.routing.RouteTracker;
import org.apache.http.impl.conn.ConnectionShutdownException;
import org.apache.http.nio.NHttpClientConnection;
import org.apache.http.nio.conn.NHttpClientConnectionManager;
import org.apache.http.nio.protocol.HttpAsyncClientExchangeHandler;
import org.apache.http.nio.protocol.HttpAsyncRequestExecutor;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.Asserts;

/**
 * Abstract {@link org.apache.http.nio.protocol.HttpAsyncClientExchangeHandler} class
 * that implements connection management aspects shared by all HTTP exchange handlers.
 * 

* Instances of this class are expected to be accessed by one thread at a time only. * The {@link #cancel()} method can be called concurrently by multiple threads. */ abstract class AbstractClientExchangeHandler implements HttpAsyncClientExchangeHandler { private static final AtomicLong COUNTER = new AtomicLong(1); protected final Log log; private final long id; private final HttpClientContext localContext; private final NHttpClientConnectionManager connmgr; private final ConnectionReuseStrategy connReuseStrategy; private final ConnectionKeepAliveStrategy keepaliveStrategy; private final AtomicReference> connectionFutureRef; private final AtomicReference managedConnRef; private final AtomicReference routeRef; private final AtomicReference routeTrackerRef; private final AtomicBoolean routeEstablished; private final AtomicReference validDurationRef; private final AtomicReference requestRef; private final AtomicReference responseRef; private final AtomicBoolean completed; private final AtomicBoolean closed; AbstractClientExchangeHandler( final Log log, final HttpClientContext localContext, final NHttpClientConnectionManager connmgr, final ConnectionReuseStrategy connReuseStrategy, final ConnectionKeepAliveStrategy keepaliveStrategy) { super(); this.log = log; this.id = COUNTER.getAndIncrement(); this.localContext = localContext; this.connmgr = connmgr; this.connReuseStrategy = connReuseStrategy; this.keepaliveStrategy = keepaliveStrategy; this.connectionFutureRef = new AtomicReference>(null); this.managedConnRef = new AtomicReference(null); this.routeRef = new AtomicReference(null); this.routeTrackerRef = new AtomicReference(null); this.routeEstablished = new AtomicBoolean(false); this.validDurationRef = new AtomicReference(null); this.requestRef = new AtomicReference(null); this.responseRef = new AtomicReference(null); this.completed = new AtomicBoolean(false); this.closed = new AtomicBoolean(false); } final long getId() { return this.id; } final boolean isCompleted() { return this.completed.get(); } final void markCompleted() { this.completed.set(true); } final void markConnectionNonReusable() { this.validDurationRef.set(null); } final boolean isRouteEstablished() { return this.routeEstablished.get(); } final HttpRoute getRoute() { return this.routeRef.get(); } final void setRoute(final HttpRoute route) { this.routeRef.set(route); } final HttpRequestWrapper getCurrentRequest() { return this.requestRef.get(); } final void setCurrentRequest(final HttpRequestWrapper request) { this.requestRef.set(request); } final HttpResponse getCurrentResponse() { return this.responseRef.get(); } final void setCurrentResponse(final HttpResponse response) { this.responseRef.set(response); } final HttpRoute getActualRoute() { final RouteTracker routeTracker = this.routeTrackerRef.get(); return routeTracker != null ? routeTracker.toRoute() : null; } final void verifytRoute() { if (!this.routeEstablished.get() && this.routeTrackerRef.get() == null) { final NHttpClientConnection managedConn = this.managedConnRef.get(); Asserts.check(managedConn != null, "Inconsistent state: managed connection is null"); final boolean routeComplete = this.connmgr.isRouteComplete(managedConn); this.routeEstablished.set(routeComplete); if (!routeComplete) { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Start connection routing"); } final HttpRoute route = this.routeRef.get(); this.routeTrackerRef.set(new RouteTracker(route)); } else { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Connection route already established"); } } } } final void onRouteToTarget() throws IOException { final NHttpClientConnection managedConn = this.managedConnRef.get(); Asserts.check(managedConn != null, "Inconsistent state: managed connection is null"); final HttpRoute route = this.routeRef.get(); Asserts.check(route != null, "Inconsistent state: HTTP route is null"); final RouteTracker routeTracker = this.routeTrackerRef.get(); Asserts.check(routeTracker != null, "Inconsistent state: HTTP route tracker"); this.connmgr.startRoute(managedConn, route, this.localContext); routeTracker.connectTarget(route.isSecure()); } final void onRouteToProxy() throws IOException { final NHttpClientConnection managedConn = this.managedConnRef.get(); Asserts.check(managedConn != null, "Inconsistent state: managed connection is null"); final HttpRoute route = this.routeRef.get(); Asserts.check(route != null, "Inconsistent state: HTTP route is null"); final RouteTracker routeTracker = this.routeTrackerRef.get(); Asserts.check(routeTracker != null, "Inconsistent state: HTTP route tracker"); this.connmgr.startRoute(managedConn, route, this.localContext); final HttpHost proxy = route.getProxyHost(); routeTracker.connectProxy(proxy, false); } final void onRouteUpgrade() throws IOException { final NHttpClientConnection managedConn = this.managedConnRef.get(); Asserts.check(managedConn != null, "Inconsistent state: managed connection is null"); final HttpRoute route = this.routeRef.get(); Asserts.check(route != null, "Inconsistent state: HTTP route is null"); final RouteTracker routeTracker = this.routeTrackerRef.get(); Asserts.check(routeTracker != null, "Inconsistent state: HTTP route tracker"); this.connmgr.upgrade(managedConn, route, this.localContext); routeTracker.layerProtocol(route.isSecure()); } final void onRouteTunnelToTarget() { final RouteTracker routeTracker = this.routeTrackerRef.get(); Asserts.check(routeTracker != null, "Inconsistent state: HTTP route tracker"); routeTracker.tunnelTarget(false); } final void onRouteComplete() { final NHttpClientConnection managedConn = this.managedConnRef.get(); Asserts.check(managedConn != null, "Inconsistent state: managed connection is null"); final HttpRoute route = this.routeRef.get(); Asserts.check(route != null, "Inconsistent state: HTTP route is null"); if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] route completed"); } this.connmgr.routeComplete(managedConn, route, this.localContext); this.routeEstablished.set(true); this.routeTrackerRef.set(null); } final NHttpClientConnection getConnection() { return this.managedConnRef.get(); } final void releaseConnection() { final NHttpClientConnection localConn = this.managedConnRef.getAndSet(null); if (localConn != null) { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] releasing connection"); } localConn.getContext().removeAttribute(HttpAsyncRequestExecutor.HTTP_HANDLER); final Long validDuration = this.validDurationRef.get(); if (validDuration != null) { final Object userToken = this.localContext.getUserToken(); this.connmgr.releaseConnection(localConn, userToken, validDuration, TimeUnit.MILLISECONDS); } else { try { localConn.close(); if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] connection discarded"); } } catch (final IOException ex) { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] " + ex.getMessage(), ex); } } finally { this.connmgr.releaseConnection(localConn, null, 0, TimeUnit.MILLISECONDS); } } } } final void discardConnection() { final NHttpClientConnection localConn = this.managedConnRef.getAndSet(null); if (localConn != null) { try { localConn.shutdown(); if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] connection aborted"); } } catch (final IOException ex) { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] " + ex.getMessage(), ex); } } finally { this.connmgr.releaseConnection(localConn, null, 0, TimeUnit.MILLISECONDS); } } } final boolean manageConnectionPersistence() { final HttpResponse response = this.responseRef.get(); Asserts.check(response != null, "Inconsistent state: HTTP response"); final NHttpClientConnection managedConn = this.managedConnRef.get(); Asserts.check(managedConn != null, "Inconsistent state: managed connection is null"); final boolean keepAlive = managedConn.isOpen() && this.connReuseStrategy.keepAlive(response, this.localContext); if (keepAlive) { final long validDuration = this.keepaliveStrategy.getKeepAliveDuration( response, this.localContext); if (this.log.isDebugEnabled()) { final String s; if (validDuration > 0) { s = "for " + validDuration + " " + TimeUnit.MILLISECONDS; } else { s = "indefinitely"; } this.log.debug("[exchange: " + this.id + "] Connection can be kept alive " + s); } this.validDurationRef.set(validDuration); } else { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Connection cannot be kept alive"); } this.validDurationRef.set(null); } return keepAlive; } private void connectionAllocated(final NHttpClientConnection managedConn) { try { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Connection allocated: " + managedConn); } this.connectionFutureRef.set(null); this.managedConnRef.set(managedConn); if (this.closed.get()) { discardConnection(); return; } if (this.connmgr.isRouteComplete(managedConn)) { this.routeEstablished.set(true); this.routeTrackerRef.set(null); } final HttpContext context = managedConn.getContext(); synchronized (context) { context.setAttribute(HttpAsyncRequestExecutor.HTTP_HANDLER, this); if (managedConn.isStale()) { failed(new ConnectionClosedException("Connection closed")); } else { managedConn.requestOutput(); } } } catch (final ConnectionShutdownException runex) { failed(runex); } catch (final RuntimeException runex) { failed(runex); throw runex; } } private void connectionRequestFailed(final Exception ex) { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] connection request failed"); } this.connectionFutureRef.set(null); failed(ex); } private void connectionRequestCancelled() { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Connection request cancelled"); } this.connectionFutureRef.set(null); try { executionCancelled(); } finally { close(); } } final void requestConnection() { final HttpRoute route = this.routeRef.get(); if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Request connection for " + route); } discardConnection(); this.validDurationRef.set(null); this.routeTrackerRef.set(null); this.routeEstablished.set(false); final Object userToken = this.localContext.getUserToken(); final RequestConfig config = this.localContext.getRequestConfig(); this.connectionFutureRef.set(this.connmgr.requestConnection( route, userToken, config.getConnectTimeout(), config.getConnectionRequestTimeout(), TimeUnit.MILLISECONDS, new FutureCallback() { @Override public void completed(final NHttpClientConnection managedConn) { connectionAllocated(managedConn); } @Override public void failed(final Exception ex) { connectionRequestFailed(ex); } @Override public void cancelled() { connectionRequestCancelled(); } })); } abstract void start() throws HttpException, IOException; abstract void releaseResources(); abstract void executionFailed(final Exception ex); abstract boolean executionCancelled(); @Override public final void close() { if (this.closed.compareAndSet(false, true)) { discardConnection(); releaseResources(); } } @Override public final boolean isDone() { return this.completed.get(); } @Override public final void failed(final Exception ex) { if (this.closed.compareAndSet(false, true)) { try { executionFailed(ex); } finally { discardConnection(); releaseResources(); } } } @Override public final boolean cancel() { if (this.log.isDebugEnabled()) { this.log.debug("[exchange: " + this.id + "] Cancelled"); } if (this.closed.compareAndSet(false, true)) { try { final Future connectionFuture = this.connectionFutureRef.getAndSet(null); if (connectionFuture != null) { connectionFuture.cancel(true); } return executionCancelled(); } finally { discardConnection(); releaseResources(); } } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy