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 */
027package org.apache.http.impl.conn;
028
029import java.io.IOException;
030import java.net.ConnectException;
031import java.net.InetAddress;
032import java.net.InetSocketAddress;
033import java.net.NoRouteToHostException;
034import java.net.Socket;
035import java.net.SocketTimeoutException;
036
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.apache.http.HttpHost;
040import org.apache.http.annotation.Contract;
041import org.apache.http.annotation.ThreadingBehavior;
042import org.apache.http.client.protocol.HttpClientContext;
043import org.apache.http.config.Lookup;
044import org.apache.http.config.SocketConfig;
045import org.apache.http.conn.ConnectTimeoutException;
046import org.apache.http.conn.DnsResolver;
047import org.apache.http.conn.HttpClientConnectionOperator;
048import org.apache.http.conn.HttpHostConnectException;
049import org.apache.http.conn.ManagedHttpClientConnection;
050import org.apache.http.conn.SchemePortResolver;
051import org.apache.http.conn.UnsupportedSchemeException;
052import org.apache.http.conn.socket.ConnectionSocketFactory;
053import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
054import org.apache.http.protocol.HttpContext;
055import org.apache.http.util.Args;
056
057/**
058 * Default implementation of {@link HttpClientConnectionOperator} used as default in Http client,
059 * when no instance provided by user to {@link BasicHttpClientConnectionManager} or {@link
060 * PoolingHttpClientConnectionManager} constructor.
061 *
062 * @since 4.4
063 */
064@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
065public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {
066
067    static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry";
068
069    private final Log log = LogFactory.getLog(getClass());
070
071    private final Lookup<ConnectionSocketFactory> socketFactoryRegistry;
072    private final SchemePortResolver schemePortResolver;
073    private final DnsResolver dnsResolver;
074
075    public DefaultHttpClientConnectionOperator(
076            final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
077            final SchemePortResolver schemePortResolver,
078            final DnsResolver dnsResolver) {
079        super();
080        Args.notNull(socketFactoryRegistry, "Socket factory registry");
081        this.socketFactoryRegistry = socketFactoryRegistry;
082        this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
083            DefaultSchemePortResolver.INSTANCE;
084        this.dnsResolver = dnsResolver != null ? dnsResolver :
085            SystemDefaultDnsResolver.INSTANCE;
086    }
087
088    @SuppressWarnings("unchecked")
089    private Lookup<ConnectionSocketFactory> getSocketFactoryRegistry(final HttpContext context) {
090        Lookup<ConnectionSocketFactory> reg = (Lookup<ConnectionSocketFactory>) context.getAttribute(
091                SOCKET_FACTORY_REGISTRY);
092        if (reg == null) {
093            reg = this.socketFactoryRegistry;
094        }
095        return reg;
096    }
097
098    @Override
099    public void connect(
100            final ManagedHttpClientConnection conn,
101            final HttpHost host,
102            final InetSocketAddress localAddress,
103            final int connectTimeout,
104            final SocketConfig socketConfig,
105            final HttpContext context) throws IOException {
106        final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
107        final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
108        if (sf == null) {
109            throw new UnsupportedSchemeException(host.getSchemeName() +
110                    " protocol is not supported");
111        }
112        final InetAddress[] addresses = host.getAddress() != null ?
113                new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());
114        final int port = this.schemePortResolver.resolve(host);
115        for (int i = 0; i < addresses.length; i++) {
116            final InetAddress address = addresses[i];
117            final boolean last = i == addresses.length - 1;
118
119            Socket sock = sf.createSocket(context);
120            sock.setSoTimeout(socketConfig.getSoTimeout());
121            sock.setReuseAddress(socketConfig.isSoReuseAddress());
122            sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
123            sock.setKeepAlive(socketConfig.isSoKeepAlive());
124            if (socketConfig.getRcvBufSize() > 0) {
125                sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
126            }
127            if (socketConfig.getSndBufSize() > 0) {
128                sock.setSendBufferSize(socketConfig.getSndBufSize());
129            }
130
131            final int linger = socketConfig.getSoLinger();
132            if (linger >= 0) {
133                sock.setSoLinger(true, linger);
134            }
135            conn.bind(sock);
136
137            final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
138            if (this.log.isDebugEnabled()) {
139                this.log.debug("Connecting to " + remoteAddress);
140            }
141            try {
142                sock = sf.connectSocket(
143                        connectTimeout, sock, host, remoteAddress, localAddress, context);
144                conn.bind(sock);
145                if (this.log.isDebugEnabled()) {
146                    this.log.debug("Connection established " + conn);
147                }
148                return;
149            } catch (final SocketTimeoutException ex) {
150                if (last) {
151                    throw new ConnectTimeoutException(ex, host, addresses);
152                }
153            } catch (final ConnectException ex) {
154                if (last) {
155                    final String msg = ex.getMessage();
156                    if ("Connection timed out".equals(msg)) {
157                        throw new ConnectTimeoutException(ex, host, addresses);
158                    } else {
159                        throw new HttpHostConnectException(ex, host, addresses);
160                    }
161                }
162            } catch (final NoRouteToHostException ex) {
163                if (last) {
164                    throw ex;
165                }
166            }
167            if (this.log.isDebugEnabled()) {
168                this.log.debug("Connect to " + remoteAddress + " timed out. " +
169                        "Connection will be retried using another IP address");
170            }
171        }
172    }
173
174    @Override
175    public void upgrade(
176            final ManagedHttpClientConnection conn,
177            final HttpHost host,
178            final HttpContext context) throws IOException {
179        final HttpClientContext clientContext = HttpClientContext.adapt(context);
180        final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(clientContext);
181        final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
182        if (sf == null) {
183            throw new UnsupportedSchemeException(host.getSchemeName() +
184                    " protocol is not supported");
185        }
186        if (!(sf instanceof LayeredConnectionSocketFactory)) {
187            throw new UnsupportedSchemeException(host.getSchemeName() +
188                    " protocol does not support connection upgrade");
189        }
190        final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf;
191        Socket sock = conn.getSocket();
192        final int port = this.schemePortResolver.resolve(host);
193        sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context);
194        conn.bind(sock);
195    }
196
197}