001/*
002 * ====================================================================
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *   http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied.  See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 * ====================================================================
020 *
021 * This software consists of voluntary contributions made by many
022 * individuals on behalf of the Apache Software Foundation.  For more
023 * information on the Apache Software Foundation, please see
024 * <http://www.apache.org/>.
025 *
026 */
027
028package org.apache.http.impl.conn;
029
030import java.io.Closeable;
031import java.io.IOException;
032import java.net.InetSocketAddress;
033import java.util.Date;
034import java.util.concurrent.TimeUnit;
035import java.util.concurrent.atomic.AtomicBoolean;
036
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.apache.http.HttpClientConnection;
040import org.apache.http.HttpHost;
041import org.apache.http.annotation.Contract;
042import org.apache.http.annotation.ThreadingBehavior;
043import org.apache.http.config.ConnectionConfig;
044import org.apache.http.config.Lookup;
045import org.apache.http.config.Registry;
046import org.apache.http.config.RegistryBuilder;
047import org.apache.http.config.SocketConfig;
048import org.apache.http.conn.ConnectionRequest;
049import org.apache.http.conn.DnsResolver;
050import org.apache.http.conn.HttpClientConnectionManager;
051import org.apache.http.conn.HttpClientConnectionOperator;
052import org.apache.http.conn.HttpConnectionFactory;
053import org.apache.http.conn.ManagedHttpClientConnection;
054import org.apache.http.conn.SchemePortResolver;
055import org.apache.http.conn.routing.HttpRoute;
056import org.apache.http.conn.socket.ConnectionSocketFactory;
057import org.apache.http.conn.socket.PlainConnectionSocketFactory;
058import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
059import org.apache.http.protocol.HttpContext;
060import org.apache.http.util.Args;
061import org.apache.http.util.Asserts;
062import org.apache.http.util.LangUtils;
063
064/**
065 * A connection manager for a single connection. This connection manager maintains only one active
066 * connection. Even though this class is fully thread-safe it ought to be used by one execution
067 * thread only, as only one thread a time can lease the connection at a time.
068 * <p>
069 * This connection manager will make an effort to reuse the connection for subsequent requests
070 * with the same {@link HttpRoute route}. It will, however, close the existing connection and
071 * open it for the given route, if the route of the persistent connection does not match that
072 * of the connection request. If the connection has been already been allocated
073 * {@link IllegalStateException} is thrown.
074 * </p>
075 * <p>
076 * This connection manager implementation should be used inside an EJB container instead of
077 * {@link PoolingHttpClientConnectionManager}.
078 * </p>
079 *
080 * @since 4.3
081 */
082@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
083public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable {
084
085    private final Log log = LogFactory.getLog(getClass());
086
087    private final HttpClientConnectionOperator connectionOperator;
088    private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
089
090    private ManagedHttpClientConnection conn;
091    private HttpRoute route;
092    private Object state;
093    private long updated;
094    private long expiry;
095    private boolean leased;
096    private SocketConfig socketConfig;
097    private ConnectionConfig connConfig;
098
099    private final AtomicBoolean isShutdown;
100
101    private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
102        return RegistryBuilder.<ConnectionSocketFactory>create()
103                .register("http", PlainConnectionSocketFactory.getSocketFactory())
104                .register("https", SSLConnectionSocketFactory.getSocketFactory())
105                .build();
106    }
107
108    public BasicHttpClientConnectionManager(
109        final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
110        final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
111        final SchemePortResolver schemePortResolver,
112        final DnsResolver dnsResolver) {
113      this(
114          new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
115          connFactory
116      );
117    }
118
119    /**
120     * @since 4.4
121     */
122    public BasicHttpClientConnectionManager(
123            final HttpClientConnectionOperator httpClientConnectionOperator,
124            final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
125        super();
126        this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
127        this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
128        this.expiry = Long.MAX_VALUE;
129        this.socketConfig = SocketConfig.DEFAULT;
130        this.connConfig = ConnectionConfig.DEFAULT;
131        this.isShutdown = new AtomicBoolean(false);
132    }
133
134    public BasicHttpClientConnectionManager(
135            final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
136            final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
137        this(socketFactoryRegistry, connFactory, null, null);
138    }
139
140    public BasicHttpClientConnectionManager(
141            final Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
142        this(socketFactoryRegistry, null, null, null);
143    }
144
145    public BasicHttpClientConnectionManager() {
146        this(getDefaultRegistry(), null, null, null);
147    }
148
149    @Override
150    protected void finalize() throws Throwable {
151        try {
152            shutdown();
153        } finally { // Make sure we call overridden method even if shutdown barfs
154            super.finalize();
155        }
156    }
157
158    @Override
159    public void close() {
160        shutdown();
161    }
162
163    HttpRoute getRoute() {
164        return route;
165    }
166
167    Object getState() {
168        return state;
169    }
170
171    public synchronized SocketConfig getSocketConfig() {
172        return socketConfig;
173    }
174
175    public synchronized void setSocketConfig(final SocketConfig socketConfig) {
176        this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
177    }
178
179    public synchronized ConnectionConfig getConnectionConfig() {
180        return connConfig;
181    }
182
183    public synchronized void setConnectionConfig(final ConnectionConfig connConfig) {
184        this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT;
185    }
186
187    @Override
188    public final ConnectionRequest requestConnection(
189            final HttpRoute route,
190            final Object state) {
191        Args.notNull(route, "Route");
192        return new ConnectionRequest() {
193
194            @Override
195            public boolean cancel() {
196                // Nothing to abort, since requests are immediate.
197                return false;
198            }
199
200            @Override
201            public HttpClientConnection get(final long timeout, final TimeUnit tunit) {
202                return BasicHttpClientConnectionManager.this.getConnection(
203                        route, state);
204            }
205
206        };
207    }
208
209    private void closeConnection() {
210        if (this.conn != null) {
211            this.log.debug("Closing connection");
212            try {
213                this.conn.close();
214            } catch (final IOException iox) {
215                if (this.log.isDebugEnabled()) {
216                    this.log.debug("I/O exception closing connection", iox);
217                }
218            }
219            this.conn = null;
220        }
221    }
222
223    private void shutdownConnection() {
224        if (this.conn != null) {
225            this.log.debug("Shutting down connection");
226            try {
227                this.conn.shutdown();
228            } catch (final IOException iox) {
229                if (this.log.isDebugEnabled()) {
230                    this.log.debug("I/O exception shutting down connection", iox);
231                }
232            }
233            this.conn = null;
234        }
235    }
236
237    private void checkExpiry() {
238        if (this.conn != null && System.currentTimeMillis() >= this.expiry) {
239            if (this.log.isDebugEnabled()) {
240                this.log.debug("Connection expired @ " + new Date(this.expiry));
241            }
242            closeConnection();
243        }
244    }
245
246    synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) {
247        Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down");
248        if (this.log.isDebugEnabled()) {
249            this.log.debug("Get connection for route " + route);
250        }
251        Asserts.check(!this.leased, "Connection is still allocated");
252        if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) {
253            closeConnection();
254        }
255        this.route = route;
256        this.state = state;
257        checkExpiry();
258        if (this.conn == null) {
259            this.conn = this.connFactory.create(route, this.connConfig);
260        }
261        this.leased = true;
262        return this.conn;
263    }
264
265    @Override
266    public synchronized void releaseConnection(
267            final HttpClientConnection conn,
268            final Object state,
269            final long keepalive, final TimeUnit tunit) {
270        Args.notNull(conn, "Connection");
271        Asserts.check(conn == this.conn, "Connection not obtained from this manager");
272        if (this.log.isDebugEnabled()) {
273            this.log.debug("Releasing connection " + conn);
274        }
275        if (this.isShutdown.get()) {
276            return;
277        }
278        try {
279            this.updated = System.currentTimeMillis();
280            if (!this.conn.isOpen()) {
281                this.conn = null;
282                this.route = null;
283                this.conn = null;
284                this.expiry = Long.MAX_VALUE;
285            } else {
286                this.state = state;
287                if (this.log.isDebugEnabled()) {
288                    final String s;
289                    if (keepalive > 0) {
290                        s = "for " + keepalive + " " + tunit;
291                    } else {
292                        s = "indefinitely";
293                    }
294                    this.log.debug("Connection can be kept alive " + s);
295                }
296                if (keepalive > 0) {
297                    this.expiry = this.updated + tunit.toMillis(keepalive);
298                } else {
299                    this.expiry = Long.MAX_VALUE;
300                }
301            }
302        } finally {
303            this.leased = false;
304        }
305    }
306
307    @Override
308    public void connect(
309            final HttpClientConnection conn,
310            final HttpRoute route,
311            final int connectTimeout,
312            final HttpContext context) throws IOException {
313        Args.notNull(conn, "Connection");
314        Args.notNull(route, "HTTP route");
315        Asserts.check(conn == this.conn, "Connection not obtained from this manager");
316        final HttpHost host;
317        if (route.getProxyHost() != null) {
318            host = route.getProxyHost();
319        } else {
320            host = route.getTargetHost();
321        }
322        final InetSocketAddress localAddress = route.getLocalSocketAddress();
323        this.connectionOperator.connect(this.conn, host, localAddress,
324                connectTimeout, this.socketConfig, context);
325    }
326
327    @Override
328    public void upgrade(
329            final HttpClientConnection conn,
330            final HttpRoute route,
331            final HttpContext context) throws IOException {
332        Args.notNull(conn, "Connection");
333        Args.notNull(route, "HTTP route");
334        Asserts.check(conn == this.conn, "Connection not obtained from this manager");
335        this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context);
336    }
337
338    @Override
339    public void routeComplete(
340            final HttpClientConnection conn,
341            final HttpRoute route,
342            final HttpContext context) throws IOException {
343    }
344
345    @Override
346    public synchronized void closeExpiredConnections() {
347        if (this.isShutdown.get()) {
348            return;
349        }
350        if (!this.leased) {
351            checkExpiry();
352        }
353    }
354
355    @Override
356    public synchronized void closeIdleConnections(final long idletime, final TimeUnit tunit) {
357        Args.notNull(tunit, "Time unit");
358        if (this.isShutdown.get()) {
359            return;
360        }
361        if (!this.leased) {
362            long time = tunit.toMillis(idletime);
363            if (time < 0) {
364                time = 0;
365            }
366            final long deadline = System.currentTimeMillis() - time;
367            if (this.updated <= deadline) {
368                closeConnection();
369            }
370        }
371    }
372
373    @Override
374    public synchronized void shutdown() {
375        if (this.isShutdown.compareAndSet(false, true)) {
376            shutdownConnection();
377        }
378    }
379
380}