/** * Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed 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. */ package org.apache.synapse.transport.passthru.connections; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHost; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.nio.NHttpClientConnection; import org.apache.http.nio.reactor.ConnectingIOReactor; import org.apache.synapse.transport.passthru.ConnectCallback; import org.apache.synapse.transport.passthru.PassThroughConstants; import org.apache.synapse.transport.passthru.TargetContext; import org.apache.synapse.transport.passthru.config.TargetConfiguration; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Manages the connection from transport to the back end servers. It keeps track of the * connections for host:port pair. */ public class TargetConnections { private static final Log log = LogFactory.getLog(TargetConnections.class); /** map to hold the ConnectionPools. The key is host:port */ private final Map<HttpRoute, HostConnections> poolMap = new ConcurrentHashMap<HttpRoute, HostConnections>(); private final String sslSchemaName = "https"; /** max connections per host:port pair. At the moment all the host:ports can * have the same max */ private int maxConnections; /** io-reactor to use for creating connections */ private ConnectingIOReactor ioReactor; /** callback invoked when a connection is made */ private ConnectCallback callback = null; /** * Create a TargetConnections with the given IO-Reactor * * @param ioReactor the IO-Reactor * @param targetConfiguration the configuration of the sender * @param callback the callback */ public TargetConnections(ConnectingIOReactor ioReactor, TargetConfiguration targetConfiguration, ConnectCallback callback) { this.maxConnections = targetConfiguration.getMaxConnections(); this.ioReactor = ioReactor; this.callback = callback; } /** * Return a connection to the host:port pair. If a connection is not available * return <code>null</code>. If the particular host:port allows to create more connections * this method will try to connect asynchronously. If the connection is successful it will * be notified in a separate thread. * * @param route Http route * @return Either returns a connection if already available or returns null and notifies * the delivery agent when the connection is available */ public NHttpClientConnection getConnection(HttpRoute route) { if (log.isDebugEnabled()) { log.debug("Trying to get a connection " + route); } HostConnections pool = getConnectionPool(route); // trying to get an existing connection NHttpClientConnection connection = pool.getConnection(); if (connection == null) { if (pool.canHaveMoreConnections()) { HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost(); ioReactor.connect(new InetSocketAddress(host.getHostName(), host.getPort()), null, pool, callback); } else { log.warn("Connection pool reached maximum allowed connections for route " + route + ". Target server may have become slow"); } } return connection; } public NHttpClientConnection getExistingConnection(HttpRoute route) { if (log.isDebugEnabled()) { log.debug("Trying to get a existing connection connection " + route); } HostConnections pool = getConnectionPool(route); return pool.getConnection(); } /** * This connection is no longer valid. So we need to shutdownConnection connection. * * @param conn connection to shutdownConnection */ public void shutdownConnection(NHttpClientConnection conn) { shutdownConnection(conn, false); } /** * This connection is no longer valid. So we need to shutdownConnection connection. * * @param conn connection to shutdownConnection * @param isError whether an error is causing this shutdown of the connection. * It is very important to set this flag correctly. * When an error causing the shutdown of the connections we should not * release associated writer buffer to the pool as it might lead into * situations like same buffer is getting released to both source and target * buffer factories */ public void shutdownConnection(NHttpClientConnection conn, boolean isError) { HostConnections pool = (HostConnections) conn.getContext().getAttribute( PassThroughConstants.CONNECTION_POOL); TargetContext.get(conn).reset(isError); if (pool != null) { pool.forget(conn); } else { // we shouldn't get here log.fatal("Connection without a pool. Something wrong. Need to fix."); } try { conn.shutdown(); } catch (IOException ignored) { } } /** * Release an active connection to the pool * * @param conn connection to be released */ public void releaseConnection(NHttpClientConnection conn) { HostConnections pool = (HostConnections) conn.getContext().getAttribute( PassThroughConstants.CONNECTION_POOL); TargetContext.get(conn).reset(false); if (pool != null) { pool.release(conn); } else { // we shouldn't get here log.fatal("Connection without a pool. Something wrong. Need to fix."); } } /** * This method is called when a new connection is made. * * @param conn connection to the target server */ public void addConnection(NHttpClientConnection conn) { HostConnections pool = (HostConnections) conn.getContext().getAttribute( PassThroughConstants.CONNECTION_POOL); if (pool != null) { pool.addConnection(conn); } else { // we shouldn't get here log.fatal("Connection without a pool. Something wrong. Need to fix."); } } private HostConnections getConnectionPool(HttpRoute route) { // see weather a pool already exists for this host:port synchronized (poolMap) { HostConnections pool = poolMap.get(route); if (pool == null) { pool = new HostConnections(route, maxConnections); poolMap.put(route, pool); } return pool; } } /** * Shutdown the connections of the given host:port list. This will allow to create new connection * at the next request happens. * * @param hostList Set of String which contains entries in hots:port format */ public void resetConnectionPool(Set<String> hostList) { for (String host : hostList) { String[] params = host.split(":"); for (Map.Entry<HttpRoute, HostConnections> connectionsEntry : poolMap.entrySet()) { HttpRoute httpRoute = connectionsEntry.getKey(); if (params.length > 1 && params[0].equalsIgnoreCase(httpRoute.getTargetHost().getHostName()) && (Integer.valueOf(params[1]) == (httpRoute.getTargetHost().getPort())) && httpRoute.getTargetHost().getSchemeName().equalsIgnoreCase(sslSchemaName)) { try { NHttpClientConnection connection = connectionsEntry.getValue().getConnection(); if (connection != null && connection.getContext() != null) { shutdownConnection(connectionsEntry.getValue().getConnection()); log.info("Connection " + httpRoute.getTargetHost().getHostName() + ":" + httpRoute.getTargetHost().getPort() + " Successful"); } else { log.debug("Error shutdown connection for " + httpRoute.getTargetHost().getHostName() + " " + httpRoute.getTargetHost().getPort() + " - Connection not available"); } } catch (Exception e) { log.warn("Error shutdown connection for " + httpRoute.getTargetHost().getHostName() + " " + httpRoute.getTargetHost().getPort() + " ", e); } } } } } }