1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java $
3 * $Revision: 652193 $
4 * $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $
5 *
6 * ====================================================================
7 * Licensed to the Apache Software Foundation (ASF) under one
8 * or more contributor license agreements.  See the NOTICE file
9 * distributed with this work for additional information
10 * regarding copyright ownership.  The ASF licenses this file
11 * to you under the Apache License, Version 2.0 (the
12 * "License"); you may not use this file except in compliance
13 * with the License.  You may obtain a copy of the License at
14 *
15 *   http://www.apache.org/licenses/LICENSE-2.0
16 *
17 * Unless required by applicable law or agreed to in writing,
18 * software distributed under the License is distributed on an
19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20 * KIND, either express or implied.  See the License for the
21 * specific language governing permissions and limitations
22 * under the License.
23 * ====================================================================
24 *
25 * This software consists of voluntary contributions made by many
26 * individuals on behalf of the Apache Software Foundation.  For more
27 * information on the Apache Software Foundation, please see
28 * <http://www.apache.org/>.
29 *
30 */
31
32package org.apache.http.impl.conn;
33
34import java.io.IOException;
35import java.net.ConnectException;
36import java.net.Socket;
37import java.net.InetAddress;
38
39import java.net.SocketException;
40import org.apache.http.HttpHost;
41import org.apache.http.params.HttpParams;
42import org.apache.http.params.HttpConnectionParams;
43import org.apache.http.protocol.HttpContext;
44
45import org.apache.http.conn.HttpHostConnectException;
46import org.apache.http.conn.OperatedClientConnection;
47import org.apache.http.conn.ClientConnectionOperator;
48import org.apache.http.conn.ConnectTimeoutException;
49import org.apache.http.conn.scheme.LayeredSocketFactory;
50import org.apache.http.conn.scheme.PlainSocketFactory;
51import org.apache.http.conn.scheme.Scheme;
52import org.apache.http.conn.scheme.SchemeRegistry;
53import org.apache.http.conn.scheme.SocketFactory;
54
55
56/**
57 * Default implementation of a
58 * {@link ClientConnectionOperator ClientConnectionOperator}.
59 * It uses a {@link SchemeRegistry SchemeRegistry} to look up
60 * {@link SocketFactory SocketFactory} objects.
61 *
62 * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
63 *
64 *
65 * <!-- empty lines to avoid svn diff problems -->
66 * @version   $Revision: 652193 $ $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $
67 *
68 * @since 4.0
69 */
70public class DefaultClientConnectionOperator
71    implements ClientConnectionOperator {
72
73    private static final PlainSocketFactory staticPlainSocketFactory = new PlainSocketFactory();
74
75    /** The scheme registry for looking up socket factories. */
76    protected SchemeRegistry schemeRegistry;
77
78
79    /**
80     * Creates a new client connection operator for the given scheme registry.
81     *
82     * @param schemes   the scheme registry
83     */
84    public DefaultClientConnectionOperator(SchemeRegistry schemes) {
85        if (schemes == null) {
86            throw new IllegalArgumentException
87                ("Scheme registry must not be null.");
88        }
89        schemeRegistry = schemes;
90    }
91
92
93    // non-javadoc, see interface ClientConnectionOperator
94    public OperatedClientConnection createConnection() {
95        return new DefaultClientConnection();
96    }
97
98
99    // non-javadoc, see interface ClientConnectionOperator
100    public void openConnection(OperatedClientConnection conn,
101                               HttpHost target,
102                               InetAddress local,
103                               HttpContext context,
104                               HttpParams params)
105        throws IOException {
106
107        if (conn == null) {
108            throw new IllegalArgumentException
109                ("Connection must not be null.");
110        }
111        if (target == null) {
112            throw new IllegalArgumentException
113                ("Target host must not be null.");
114        }
115        // local address may be null
116        //@@@ is context allowed to be null?
117        if (params == null) {
118            throw new IllegalArgumentException
119                ("Parameters must not be null.");
120        }
121        if (conn.isOpen()) {
122            throw new IllegalArgumentException
123                ("Connection must not be open.");
124        }
125
126        final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
127        final SocketFactory sf = schm.getSocketFactory();
128        final SocketFactory plain_sf;
129        final LayeredSocketFactory layered_sf;
130        if (sf instanceof LayeredSocketFactory) {
131            plain_sf = staticPlainSocketFactory;
132            layered_sf = (LayeredSocketFactory)sf;
133        } else {
134            plain_sf = sf;
135            layered_sf = null;
136        }
137        InetAddress[] addresses = InetAddress.getAllByName(target.getHostName());
138
139        for (int i = 0; i < addresses.length; ++i) {
140            Socket sock = plain_sf.createSocket();
141            conn.opening(sock, target);
142
143            try {
144                Socket connsock = plain_sf.connectSocket(sock,
145                    addresses[i].getHostAddress(),
146                    schm.resolvePort(target.getPort()),
147                    local, 0, params);
148                if (sock != connsock) {
149                    sock = connsock;
150                    conn.opening(sock, target);
151                }
152                /*
153                 * prepareSocket is called on the just connected
154                 * socket before the creation of the layered socket to
155                 * ensure that desired socket options such as
156                 * TCP_NODELAY, SO_RCVTIMEO, SO_LINGER will be set
157                 * before any I/O is performed on the socket. This
158                 * happens in the common case as
159                 * SSLSocketFactory.createSocket performs hostname
160                 * verification which requires that SSL handshaking be
161                 * performed.
162                 */
163                prepareSocket(sock, context, params);
164                if (layered_sf != null) {
165                    Socket layeredsock = layered_sf.createSocket(sock,
166                        target.getHostName(),
167                        schm.resolvePort(target.getPort()),
168                        true);
169                    if (layeredsock != sock) {
170                        conn.opening(layeredsock, target);
171                    }
172                    conn.openCompleted(sf.isSecure(layeredsock), params);
173                } else {
174                    conn.openCompleted(sf.isSecure(sock), params);
175                }
176                break;
177            // BEGIN android-changed
178            //       catch SocketException to cover any kind of connect failure
179            } catch (SocketException ex) {
180                if (i == addresses.length - 1) {
181                    ConnectException cause = ex instanceof ConnectException
182                            ? (ConnectException) ex : new ConnectException(ex.getMessage(), ex);
183                    throw new HttpHostConnectException(target, cause);
184                }
185            // END android-changed
186            } catch (ConnectTimeoutException ex) {
187                if (i == addresses.length - 1) {
188                    throw ex;
189                }
190            }
191        }
192    } // openConnection
193
194
195    // non-javadoc, see interface ClientConnectionOperator
196    public void updateSecureConnection(OperatedClientConnection conn,
197                                       HttpHost target,
198                                       HttpContext context,
199                                       HttpParams params)
200        throws IOException {
201
202
203        if (conn == null) {
204            throw new IllegalArgumentException
205                ("Connection must not be null.");
206        }
207        if (target == null) {
208            throw new IllegalArgumentException
209                ("Target host must not be null.");
210        }
211        //@@@ is context allowed to be null?
212        if (params == null) {
213            throw new IllegalArgumentException
214                ("Parameters must not be null.");
215        }
216        if (!conn.isOpen()) {
217            throw new IllegalArgumentException
218                ("Connection must be open.");
219        }
220
221        final Scheme schm = schemeRegistry.getScheme(target.getSchemeName());
222        if (!(schm.getSocketFactory() instanceof LayeredSocketFactory)) {
223            throw new IllegalArgumentException
224                ("Target scheme (" + schm.getName() +
225                 ") must have layered socket factory.");
226        }
227
228        final LayeredSocketFactory lsf = (LayeredSocketFactory) schm.getSocketFactory();
229        final Socket sock;
230        try {
231            sock = lsf.createSocket
232                (conn.getSocket(), target.getHostName(), schm.resolvePort(target.getPort()), true);
233        } catch (ConnectException ex) {
234            throw new HttpHostConnectException(target, ex);
235        }
236        prepareSocket(sock, context, params);
237        conn.update(sock, target, lsf.isSecure(sock), params);
238        //@@@ error handling: close the layered socket in case of exception?
239
240    } // updateSecureConnection
241
242
243    /**
244     * Performs standard initializations on a newly created socket.
245     *
246     * @param sock      the socket to prepare
247     * @param context   the context for the connection
248     * @param params    the parameters from which to prepare the socket
249     *
250     * @throws IOException      in case of an IO problem
251     */
252    protected void prepareSocket(Socket sock, HttpContext context,
253                                 HttpParams params)
254        throws IOException {
255
256        // context currently not used, but derived classes may need it
257        //@@@ is context allowed to be null?
258
259        sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params));
260        sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params));
261
262        int linger = HttpConnectionParams.getLinger(params);
263        if (linger >= 0) {
264            sock.setSoLinger(linger > 0, linger);
265        }
266
267    } // prepareSocket
268
269
270} // class DefaultClientConnectionOperator
271