|
1 /* |
|
2 * ==================================================================== |
|
3 * Licensed to the Apache Software Foundation (ASF) under one |
|
4 * or more contributor license agreements. See the NOTICE file |
|
5 * distributed with this work for additional information |
|
6 * regarding copyright ownership. The ASF licenses this file |
|
7 * to you under the Apache License, Version 2.0 (the |
|
8 * "License"); you may not use this file except in compliance |
|
9 * with the License. You may obtain a copy of the License at |
|
10 * |
|
11 * http://www.apache.org/licenses/LICENSE-2.0 |
|
12 * |
|
13 * Unless required by applicable law or agreed to in writing, |
|
14 * software distributed under the License is distributed on an |
|
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
16 * KIND, either express or implied. See the License for the |
|
17 * specific language governing permissions and limitations |
|
18 * under the License. |
|
19 * ==================================================================== |
|
20 * |
|
21 * This software consists of voluntary contributions made by many |
|
22 * individuals on behalf of the Apache Software Foundation. For more |
|
23 * information on the Apache Software Foundation, please see |
|
24 * <http://www.apache.org/>. |
|
25 * |
|
26 */ |
|
27 |
|
28 package ch.boye.httpclientandroidlib.impl.conn; |
|
29 |
|
30 import java.io.IOException; |
|
31 import java.net.ConnectException; |
|
32 import java.net.InetSocketAddress; |
|
33 import java.net.Socket; |
|
34 import java.net.InetAddress; |
|
35 import java.net.UnknownHostException; |
|
36 |
|
37 import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog; |
|
38 /* LogFactory removed by HttpClient for Android script. */ |
|
39 import ch.boye.httpclientandroidlib.annotation.ThreadSafe; |
|
40 |
|
41 import ch.boye.httpclientandroidlib.HttpHost; |
|
42 import ch.boye.httpclientandroidlib.params.HttpParams; |
|
43 import ch.boye.httpclientandroidlib.params.HttpConnectionParams; |
|
44 import ch.boye.httpclientandroidlib.protocol.HttpContext; |
|
45 |
|
46 import ch.boye.httpclientandroidlib.conn.ConnectTimeoutException; |
|
47 import ch.boye.httpclientandroidlib.conn.HttpHostConnectException; |
|
48 import ch.boye.httpclientandroidlib.conn.OperatedClientConnection; |
|
49 import ch.boye.httpclientandroidlib.conn.ClientConnectionOperator; |
|
50 import ch.boye.httpclientandroidlib.conn.scheme.LayeredSchemeSocketFactory; |
|
51 import ch.boye.httpclientandroidlib.conn.scheme.Scheme; |
|
52 import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry; |
|
53 import ch.boye.httpclientandroidlib.conn.scheme.SchemeSocketFactory; |
|
54 |
|
55 /** |
|
56 * Default implementation of a {@link ClientConnectionOperator}. It uses a {@link SchemeRegistry} |
|
57 * to look up {@link SchemeSocketFactory} objects. |
|
58 * <p> |
|
59 * This connection operator is multihome network aware and will attempt to retry failed connects |
|
60 * against all known IP addresses sequentially until the connect is successful or all known |
|
61 * addresses fail to respond. Please note the same |
|
62 * {@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT} value will be used |
|
63 * for each connection attempt, so in the worst case the total elapsed time before timeout |
|
64 * can be <code>CONNECTION_TIMEOUT * n</code> where <code>n</code> is the number of IP addresses |
|
65 * of the given host. One can disable multihome support by overriding |
|
66 * the {@link #resolveHostname(String)} method and returning only one IP address for the given |
|
67 * host name. |
|
68 * <p> |
|
69 * The following parameters can be used to customize the behavior of this |
|
70 * class: |
|
71 * <ul> |
|
72 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li> |
|
73 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li> |
|
74 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_LINGER}</li> |
|
75 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li> |
|
76 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#TCP_NODELAY}</li> |
|
77 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SOCKET_BUFFER_SIZE}</li> |
|
78 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li> |
|
79 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li> |
|
80 * </ul> |
|
81 * |
|
82 * @since 4.0 |
|
83 */ |
|
84 @ThreadSafe |
|
85 public class DefaultClientConnectionOperator implements ClientConnectionOperator { |
|
86 |
|
87 public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass()); |
|
88 |
|
89 /** The scheme registry for looking up socket factories. */ |
|
90 protected final SchemeRegistry schemeRegistry; // @ThreadSafe |
|
91 |
|
92 /** |
|
93 * Creates a new client connection operator for the given scheme registry. |
|
94 * |
|
95 * @param schemes the scheme registry |
|
96 */ |
|
97 public DefaultClientConnectionOperator(final SchemeRegistry schemes) { |
|
98 if (schemes == null) { |
|
99 throw new IllegalArgumentException("Scheme registry amy not be null"); |
|
100 } |
|
101 this.schemeRegistry = schemes; |
|
102 } |
|
103 |
|
104 public OperatedClientConnection createConnection() { |
|
105 return new DefaultClientConnection(); |
|
106 } |
|
107 |
|
108 public void openConnection( |
|
109 final OperatedClientConnection conn, |
|
110 final HttpHost target, |
|
111 final InetAddress local, |
|
112 final HttpContext context, |
|
113 final HttpParams params) throws IOException { |
|
114 if (conn == null) { |
|
115 throw new IllegalArgumentException("Connection may not be null"); |
|
116 } |
|
117 if (target == null) { |
|
118 throw new IllegalArgumentException("Target host may not be null"); |
|
119 } |
|
120 if (params == null) { |
|
121 throw new IllegalArgumentException("Parameters may not be null"); |
|
122 } |
|
123 if (conn.isOpen()) { |
|
124 throw new IllegalStateException("Connection must not be open"); |
|
125 } |
|
126 |
|
127 Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); |
|
128 SchemeSocketFactory sf = schm.getSchemeSocketFactory(); |
|
129 |
|
130 InetAddress[] addresses = resolveHostname(target.getHostName()); |
|
131 int port = schm.resolvePort(target.getPort()); |
|
132 for (int i = 0; i < addresses.length; i++) { |
|
133 InetAddress address = addresses[i]; |
|
134 boolean last = i == addresses.length - 1; |
|
135 |
|
136 Socket sock = sf.createSocket(params); |
|
137 conn.opening(sock, target); |
|
138 |
|
139 InetSocketAddress remoteAddress = new HttpInetSocketAddress(target, address, port); |
|
140 InetSocketAddress localAddress = null; |
|
141 if (local != null) { |
|
142 localAddress = new InetSocketAddress(local, 0); |
|
143 } |
|
144 if (this.log.isDebugEnabled()) { |
|
145 this.log.debug("Connecting to " + remoteAddress); |
|
146 } |
|
147 try { |
|
148 Socket connsock = sf.connectSocket(sock, remoteAddress, localAddress, params); |
|
149 if (sock != connsock) { |
|
150 sock = connsock; |
|
151 conn.opening(sock, target); |
|
152 } |
|
153 prepareSocket(sock, context, params); |
|
154 conn.openCompleted(sf.isSecure(sock), params); |
|
155 return; |
|
156 } catch (ConnectException ex) { |
|
157 if (last) { |
|
158 throw new HttpHostConnectException(target, ex); |
|
159 } |
|
160 } catch (ConnectTimeoutException ex) { |
|
161 if (last) { |
|
162 throw ex; |
|
163 } |
|
164 } |
|
165 if (this.log.isDebugEnabled()) { |
|
166 this.log.debug("Connect to " + remoteAddress + " timed out. " + |
|
167 "Connection will be retried using another IP address"); |
|
168 } |
|
169 } |
|
170 } |
|
171 |
|
172 public void updateSecureConnection( |
|
173 final OperatedClientConnection conn, |
|
174 final HttpHost target, |
|
175 final HttpContext context, |
|
176 final HttpParams params) throws IOException { |
|
177 if (conn == null) { |
|
178 throw new IllegalArgumentException("Connection may not be null"); |
|
179 } |
|
180 if (target == null) { |
|
181 throw new IllegalArgumentException("Target host may not be null"); |
|
182 } |
|
183 if (params == null) { |
|
184 throw new IllegalArgumentException("Parameters may not be null"); |
|
185 } |
|
186 if (!conn.isOpen()) { |
|
187 throw new IllegalStateException("Connection must be open"); |
|
188 } |
|
189 |
|
190 final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); |
|
191 if (!(schm.getSchemeSocketFactory() instanceof LayeredSchemeSocketFactory)) { |
|
192 throw new IllegalArgumentException |
|
193 ("Target scheme (" + schm.getName() + |
|
194 ") must have layered socket factory."); |
|
195 } |
|
196 |
|
197 LayeredSchemeSocketFactory lsf = (LayeredSchemeSocketFactory) schm.getSchemeSocketFactory(); |
|
198 Socket sock; |
|
199 try { |
|
200 sock = lsf.createLayeredSocket( |
|
201 conn.getSocket(), target.getHostName(), target.getPort(), true); |
|
202 } catch (ConnectException ex) { |
|
203 throw new HttpHostConnectException(target, ex); |
|
204 } |
|
205 prepareSocket(sock, context, params); |
|
206 conn.update(sock, target, lsf.isSecure(sock), params); |
|
207 } |
|
208 |
|
209 /** |
|
210 * Performs standard initializations on a newly created socket. |
|
211 * |
|
212 * @param sock the socket to prepare |
|
213 * @param context the context for the connection |
|
214 * @param params the parameters from which to prepare the socket |
|
215 * |
|
216 * @throws IOException in case of an IO problem |
|
217 */ |
|
218 protected void prepareSocket( |
|
219 final Socket sock, |
|
220 final HttpContext context, |
|
221 final HttpParams params) throws IOException { |
|
222 sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); |
|
223 sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); |
|
224 |
|
225 int linger = HttpConnectionParams.getLinger(params); |
|
226 if (linger >= 0) { |
|
227 sock.setSoLinger(linger > 0, linger); |
|
228 } |
|
229 } |
|
230 |
|
231 /** |
|
232 * Resolves the given host name to an array of corresponding IP addresses, based on the |
|
233 * configured name service on the system. |
|
234 * |
|
235 * @param host host name to resolve |
|
236 * @return array of IP addresses |
|
237 * @exception UnknownHostException if no IP address for the host could be determined. |
|
238 * |
|
239 * @since 4.1 |
|
240 */ |
|
241 protected InetAddress[] resolveHostname(final String host) throws UnknownHostException { |
|
242 return InetAddress.getAllByName(host); |
|
243 } |
|
244 |
|
245 } |
|
246 |