michael@0: /* michael@0: * ==================================================================== michael@0: * Licensed to the Apache Software Foundation (ASF) under one michael@0: * or more contributor license agreements. See the NOTICE file michael@0: * distributed with this work for additional information michael@0: * regarding copyright ownership. The ASF licenses this file michael@0: * to you under the Apache License, Version 2.0 (the michael@0: * "License"); you may not use this file except in compliance michael@0: * with the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, michael@0: * software distributed under the License is distributed on an michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY michael@0: * KIND, either express or implied. See the License for the michael@0: * specific language governing permissions and limitations michael@0: * under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.impl.conn; michael@0: michael@0: import java.io.IOException; michael@0: import java.net.ConnectException; michael@0: import java.net.InetSocketAddress; michael@0: import java.net.Socket; michael@0: import java.net.InetAddress; michael@0: import java.net.UnknownHostException; michael@0: michael@0: import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; michael@0: /* LogFactory removed by HttpClient for Android script. */ michael@0: import ch.boye.httpclientandroidlib.annotation.ThreadSafe; michael@0: michael@0: import ch.boye.httpclientandroidlib.HttpHost; michael@0: import ch.boye.httpclientandroidlib.params.HttpParams; michael@0: import ch.boye.httpclientandroidlib.params.HttpConnectionParams; michael@0: import ch.boye.httpclientandroidlib.protocol.HttpContext; michael@0: michael@0: import ch.boye.httpclientandroidlib.conn.ConnectTimeoutException; michael@0: import ch.boye.httpclientandroidlib.conn.HttpHostConnectException; michael@0: import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; michael@0: import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.LayeredSchemeSocketFactory; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.Scheme; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.SchemeSocketFactory; michael@0: michael@0: /** michael@0: * Default implementation of a {@link ClientConnectionOperator}. It uses a {@link SchemeRegistry} michael@0: * to look up {@link SchemeSocketFactory} objects. michael@0: *

michael@0: * This connection operator is multihome network aware and will attempt to retry failed connects michael@0: * against all known IP addresses sequentially until the connect is successful or all known michael@0: * addresses fail to respond. Please note the same michael@0: * {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT} value will be used michael@0: * for each connection attempt, so in the worst case the total elapsed time before timeout michael@0: * can be CONNECTION_TIMEOUT * n where n is the number of IP addresses michael@0: * of the given host. One can disable multihome support by overriding michael@0: * the {@link #resolveHostname(String)} method and returning only one IP address for the given michael@0: * host name. michael@0: *

michael@0: * The following parameters can be used to customize the behavior of this michael@0: * class: michael@0: *

michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: @ThreadSafe michael@0: public class DefaultClientConnectionOperator implements ClientConnectionOperator { michael@0: michael@0: public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); michael@0: michael@0: /** The scheme registry for looking up socket factories. */ michael@0: protected final SchemeRegistry schemeRegistry; // @ThreadSafe michael@0: michael@0: /** michael@0: * Creates a new client connection operator for the given scheme registry. michael@0: * michael@0: * @param schemes the scheme registry michael@0: */ michael@0: public DefaultClientConnectionOperator(final SchemeRegistry schemes) { michael@0: if (schemes == null) { michael@0: throw new IllegalArgumentException("Scheme registry amy not be null"); michael@0: } michael@0: this.schemeRegistry = schemes; michael@0: } michael@0: michael@0: public OperatedClientConnection createConnection() { michael@0: return new DefaultClientConnection(); michael@0: } michael@0: michael@0: public void openConnection( michael@0: final OperatedClientConnection conn, michael@0: final HttpHost target, michael@0: final InetAddress local, michael@0: final HttpContext context, michael@0: final HttpParams params) throws IOException { michael@0: if (conn == null) { michael@0: throw new IllegalArgumentException("Connection may not be null"); michael@0: } michael@0: if (target == null) { michael@0: throw new IllegalArgumentException("Target host may not be null"); michael@0: } michael@0: if (params == null) { michael@0: throw new IllegalArgumentException("Parameters may not be null"); michael@0: } michael@0: if (conn.isOpen()) { michael@0: throw new IllegalStateException("Connection must not be open"); michael@0: } michael@0: michael@0: Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); michael@0: SchemeSocketFactory sf = schm.getSchemeSocketFactory(); michael@0: michael@0: InetAddress[] addresses = resolveHostname(target.getHostName()); michael@0: int port = schm.resolvePort(target.getPort()); michael@0: for (int i = 0; i < addresses.length; i++) { michael@0: InetAddress address = addresses[i]; michael@0: boolean last = i == addresses.length - 1; michael@0: michael@0: Socket sock = sf.createSocket(params); michael@0: conn.opening(sock, target); michael@0: michael@0: InetSocketAddress remoteAddress = new HttpInetSocketAddress(target, address, port); michael@0: InetSocketAddress localAddress = null; michael@0: if (local != null) { michael@0: localAddress = new InetSocketAddress(local, 0); michael@0: } michael@0: if (this.log.isDebugEnabled()) { michael@0: this.log.debug("Connecting to " + remoteAddress); michael@0: } michael@0: try { michael@0: Socket connsock = sf.connectSocket(sock, remoteAddress, localAddress, params); michael@0: if (sock != connsock) { michael@0: sock = connsock; michael@0: conn.opening(sock, target); michael@0: } michael@0: prepareSocket(sock, context, params); michael@0: conn.openCompleted(sf.isSecure(sock), params); michael@0: return; michael@0: } catch (ConnectException ex) { michael@0: if (last) { michael@0: throw new HttpHostConnectException(target, ex); michael@0: } michael@0: } catch (ConnectTimeoutException ex) { michael@0: if (last) { michael@0: throw ex; michael@0: } michael@0: } michael@0: if (this.log.isDebugEnabled()) { michael@0: this.log.debug("Connect to " + remoteAddress + " timed out. " + michael@0: "Connection will be retried using another IP address"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: public void updateSecureConnection( michael@0: final OperatedClientConnection conn, michael@0: final HttpHost target, michael@0: final HttpContext context, michael@0: final HttpParams params) throws IOException { michael@0: if (conn == null) { michael@0: throw new IllegalArgumentException("Connection may not be null"); michael@0: } michael@0: if (target == null) { michael@0: throw new IllegalArgumentException("Target host may not be null"); michael@0: } michael@0: if (params == null) { michael@0: throw new IllegalArgumentException("Parameters may not be null"); michael@0: } michael@0: if (!conn.isOpen()) { michael@0: throw new IllegalStateException("Connection must be open"); michael@0: } michael@0: michael@0: final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); michael@0: if (!(schm.getSchemeSocketFactory() instanceof LayeredSchemeSocketFactory)) { michael@0: throw new IllegalArgumentException michael@0: ("Target scheme (" + schm.getName() + michael@0: ") must have layered socket factory."); michael@0: } michael@0: michael@0: LayeredSchemeSocketFactory lsf = (LayeredSchemeSocketFactory) schm.getSchemeSocketFactory(); michael@0: Socket sock; michael@0: try { michael@0: sock = lsf.createLayeredSocket( michael@0: conn.getSocket(), target.getHostName(), target.getPort(), true); michael@0: } catch (ConnectException ex) { michael@0: throw new HttpHostConnectException(target, ex); michael@0: } michael@0: prepareSocket(sock, context, params); michael@0: conn.update(sock, target, lsf.isSecure(sock), params); michael@0: } michael@0: michael@0: /** michael@0: * Performs standard initializations on a newly created socket. michael@0: * michael@0: * @param sock the socket to prepare michael@0: * @param context the context for the connection michael@0: * @param params the parameters from which to prepare the socket michael@0: * michael@0: * @throws IOException in case of an IO problem michael@0: */ michael@0: protected void prepareSocket( michael@0: final Socket sock, michael@0: final HttpContext context, michael@0: final HttpParams params) throws IOException { michael@0: sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); michael@0: sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); michael@0: michael@0: int linger = HttpConnectionParams.getLinger(params); michael@0: if (linger >= 0) { michael@0: sock.setSoLinger(linger > 0, linger); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Resolves the given host name to an array of corresponding IP addresses, based on the michael@0: * configured name service on the system. michael@0: * michael@0: * @param host host name to resolve michael@0: * @return array of IP addresses michael@0: * @exception UnknownHostException if no IP address for the host could be determined. michael@0: * michael@0: * @since 4.1 michael@0: */ michael@0: protected InetAddress[] resolveHostname(final String host) throws UnknownHostException { michael@0: return InetAddress.getAllByName(host); michael@0: } michael@0: michael@0: } michael@0: