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: * - {@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}
michael@0: * - {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}
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: