blob: 95cecd2c9b846c6309c9ef93e1894b37b1d6d876 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net.http;
18
19import android.content.Context;
20import android.os.SystemClock;
21
22import java.io.IOException;
23import java.net.UnknownHostException;
24import java.util.ListIterator;
25import java.util.LinkedList;
26
27import javax.net.ssl.SSLHandshakeException;
28
29import org.apache.http.ConnectionReuseStrategy;
30import org.apache.http.HttpEntity;
31import org.apache.http.HttpException;
32import org.apache.http.HttpHost;
33import org.apache.http.HttpVersion;
34import org.apache.http.ParseException;
35import org.apache.http.ProtocolVersion;
36import org.apache.http.protocol.ExecutionContext;
37import org.apache.http.protocol.HttpContext;
38import org.apache.http.protocol.BasicHttpContext;
39
40/**
41 * {@hide}
42 */
43abstract class Connection {
44
45 /**
46 * Allow a TCP connection 60 idle seconds before erroring out
47 */
48 static final int SOCKET_TIMEOUT = 60000;
49
50 private static final int SEND = 0;
51 private static final int READ = 1;
52 private static final int DRAIN = 2;
53 private static final int DONE = 3;
54 private static final String[] states = {"SEND", "READ", "DRAIN", "DONE"};
55
56 Context mContext;
57
58 /** The low level connection */
59 protected AndroidHttpClientConnection mHttpClientConnection = null;
60
61 /**
62 * The server SSL certificate associated with this connection
63 * (null if the connection is not secure)
64 * It would be nice to store the whole certificate chain, but
65 * we want to keep things as light-weight as possible
66 */
67 protected SslCertificate mCertificate = null;
68
69 /**
70 * The host this connection is connected to. If using proxy,
71 * this is set to the proxy address
72 */
73 HttpHost mHost;
74
75 /** true if the connection can be reused for sending more requests */
76 private boolean mCanPersist;
77
78 /** context required by ConnectionReuseStrategy. */
79 private HttpContext mHttpContext;
80
81 /** set when cancelled */
82 private static int STATE_NORMAL = 0;
83 private static int STATE_CANCEL_REQUESTED = 1;
84 private int mActive = STATE_NORMAL;
85
86 /** The number of times to try to re-connect (if connect fails). */
87 private final static int RETRY_REQUEST_LIMIT = 2;
88
89 private static final int MIN_PIPE = 2;
90 private static final int MAX_PIPE = 3;
91
92 /**
93 * Doesn't seem to exist anymore in the new HTTP client, so copied here.
94 */
95 private static final String HTTP_CONNECTION = "http.connection";
96
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 RequestFeeder mRequestFeeder;
98
99 /**
100 * Buffer for feeding response blocks to webkit. One block per
101 * connection reduces memory churn.
102 */
103 private byte[] mBuf;
104
105 protected Connection(Context context, HttpHost host,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 RequestFeeder requestFeeder) {
107 mContext = context;
108 mHost = host;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 mRequestFeeder = requestFeeder;
110
111 mCanPersist = false;
112 mHttpContext = new BasicHttpContext(null);
113 }
114
115 HttpHost getHost() {
116 return mHost;
117 }
118
119 /**
120 * connection factory: returns an HTTP or HTTPS connection as
121 * necessary
122 */
123 static Connection getConnection(
Patrick Scott86806ce12009-10-01 15:54:46 -0400124 Context context, HttpHost host, HttpHost proxy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 RequestFeeder requestFeeder) {
126
127 if (host.getSchemeName().equals("http")) {
Patrick Scott86806ce12009-10-01 15:54:46 -0400128 return new HttpConnection(context, host, requestFeeder);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 }
130
131 // Otherwise, default to https
Patrick Scott86806ce12009-10-01 15:54:46 -0400132 return new HttpsConnection(context, host, proxy, requestFeeder);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 }
134
135 /**
136 * @return The server SSL certificate associated with this
137 * connection (null if the connection is not secure)
138 */
139 /* package */ SslCertificate getCertificate() {
140 return mCertificate;
141 }
142
143 /**
144 * Close current network connection
145 * Note: this runs in non-network thread
146 */
147 void cancel() {
148 mActive = STATE_CANCEL_REQUESTED;
149 closeConnection();
150 if (HttpLog.LOGV) HttpLog.v(
151 "Connection.cancel(): connection closed " + mHost);
152 }
153
154 /**
155 * Process requests in queue
156 * pipelines requests
157 */
158 void processRequests(Request firstRequest) {
159 Request req = null;
160 boolean empty;
161 int error = EventHandler.OK;
162 Exception exception = null;
163
164 LinkedList<Request> pipe = new LinkedList<Request>();
165
166 int minPipe = MIN_PIPE, maxPipe = MAX_PIPE;
167 int state = SEND;
168
169 while (state != DONE) {
170 if (HttpLog.LOGV) HttpLog.v(
171 states[state] + " pipe " + pipe.size());
172
173 /* If a request was cancelled, give other cancel requests
174 some time to go through so we don't uselessly restart
175 connections */
176 if (mActive == STATE_CANCEL_REQUESTED) {
177 try {
178 Thread.sleep(100);
179 } catch (InterruptedException x) { /* ignore */ }
180 mActive = STATE_NORMAL;
181 }
182
183 switch (state) {
184 case SEND: {
185 if (pipe.size() == maxPipe) {
186 state = READ;
187 break;
188 }
189 /* get a request */
190 if (firstRequest == null) {
191 req = mRequestFeeder.getRequest(mHost);
192 } else {
193 req = firstRequest;
194 firstRequest = null;
195 }
196 if (req == null) {
197 state = DRAIN;
198 break;
199 }
200 req.setConnection(this);
201
202 /* Don't work on cancelled requests. */
203 if (req.mCancelled) {
204 if (HttpLog.LOGV) HttpLog.v(
205 "processRequests(): skipping cancelled request "
206 + req);
207 req.complete();
208 break;
209 }
210
211 if (mHttpClientConnection == null ||
212 !mHttpClientConnection.isOpen()) {
213 /* If this call fails, the address is bad or
214 the net is down. Punt for now.
215
216 FIXME: blow out entire queue here on
217 connection failure if net up? */
218
219 if (!openHttpConnection(req)) {
220 state = DONE;
221 break;
222 }
223 }
224
Brian Carlstromdba8cb72010-03-18 16:56:41 -0700225 /* we have a connection, let the event handler
226 * know of any associated certificate,
227 * potentially none.
228 */
229 req.mEventHandler.certificate(mCertificate);
230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 try {
232 /* FIXME: don't increment failure count if old
233 connection? There should not be a penalty for
234 attempting to reuse an old connection */
235 req.sendRequest(mHttpClientConnection);
236 } catch (HttpException e) {
237 exception = e;
238 error = EventHandler.ERROR;
239 } catch (IOException e) {
240 exception = e;
241 error = EventHandler.ERROR_IO;
242 } catch (IllegalStateException e) {
243 exception = e;
244 error = EventHandler.ERROR_IO;
245 }
246 if (exception != null) {
247 if (httpFailure(req, error, exception) &&
248 !req.mCancelled) {
249 /* retry request if not permanent failure
250 or cancelled */
251 pipe.addLast(req);
252 }
253 exception = null;
Patrick Scottaaebc862009-09-16 09:59:12 -0400254 state = clearPipe(pipe) ? DONE : SEND;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 minPipe = maxPipe = 1;
256 break;
257 }
258
259 pipe.addLast(req);
260 if (!mCanPersist) state = READ;
261 break;
262
263 }
264 case DRAIN:
265 case READ: {
266 empty = !mRequestFeeder.haveRequest(mHost);
267 int pipeSize = pipe.size();
268 if (state != DRAIN && pipeSize < minPipe &&
269 !empty && mCanPersist) {
270 state = SEND;
271 break;
272 } else if (pipeSize == 0) {
273 /* Done if no other work to do */
274 state = empty ? DONE : SEND;
275 break;
276 }
277
278 req = (Request)pipe.removeFirst();
279 if (HttpLog.LOGV) HttpLog.v(
280 "processRequests() reading " + req);
281
282 try {
283 req.readResponse(mHttpClientConnection);
284 } catch (ParseException e) {
285 exception = e;
286 error = EventHandler.ERROR_IO;
287 } catch (IOException e) {
288 exception = e;
289 error = EventHandler.ERROR_IO;
290 } catch (IllegalStateException e) {
291 exception = e;
292 error = EventHandler.ERROR_IO;
293 }
294 if (exception != null) {
295 if (httpFailure(req, error, exception) &&
296 !req.mCancelled) {
297 /* retry request if not permanent failure
298 or cancelled */
299 req.reset();
300 pipe.addFirst(req);
301 }
302 exception = null;
303 mCanPersist = false;
304 }
305 if (!mCanPersist) {
306 if (HttpLog.LOGV) HttpLog.v(
307 "processRequests(): no persist, closing " +
308 mHost);
309
310 closeConnection();
311
312 mHttpContext.removeAttribute(HTTP_CONNECTION);
313 clearPipe(pipe);
314 minPipe = maxPipe = 1;
Patrick Scottaaebc862009-09-16 09:59:12 -0400315 state = SEND;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800316 }
317 break;
318 }
319 }
320 }
321 }
322
323 /**
324 * After a send/receive failure, any pipelined requests must be
325 * cleared back to the mRequest queue
326 * @return true if mRequests is empty after pipe cleared
327 */
328 private boolean clearPipe(LinkedList<Request> pipe) {
329 boolean empty = true;
330 if (HttpLog.LOGV) HttpLog.v(
331 "Connection.clearPipe(): clearing pipe " + pipe.size());
332 synchronized (mRequestFeeder) {
333 Request tReq;
334 while (!pipe.isEmpty()) {
335 tReq = (Request)pipe.removeLast();
336 if (HttpLog.LOGV) HttpLog.v(
337 "clearPipe() adding back " + mHost + " " + tReq);
338 mRequestFeeder.requeueRequest(tReq);
339 empty = false;
340 }
Patrick Scott86806ce12009-10-01 15:54:46 -0400341 if (empty) empty = !mRequestFeeder.haveRequest(mHost);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 }
343 return empty;
344 }
345
346 /**
347 * @return true on success
348 */
349 private boolean openHttpConnection(Request req) {
350
351 long now = SystemClock.uptimeMillis();
352 int error = EventHandler.OK;
353 Exception exception = null;
354
355 try {
356 // reset the certificate to null before opening a connection
357 mCertificate = null;
358 mHttpClientConnection = openConnection(req);
359 if (mHttpClientConnection != null) {
360 mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT);
361 mHttpContext.setAttribute(HTTP_CONNECTION,
362 mHttpClientConnection);
363 } else {
364 // we tried to do SSL tunneling, failed,
365 // and need to drop the request;
366 // we have already informed the handler
367 req.mFailCount = RETRY_REQUEST_LIMIT;
368 return false;
369 }
370 } catch (UnknownHostException e) {
371 if (HttpLog.LOGV) HttpLog.v("Failed to open connection");
372 error = EventHandler.ERROR_LOOKUP;
373 exception = e;
374 } catch (IllegalArgumentException e) {
375 if (HttpLog.LOGV) HttpLog.v("Illegal argument exception");
376 error = EventHandler.ERROR_CONNECT;
377 req.mFailCount = RETRY_REQUEST_LIMIT;
378 exception = e;
379 } catch (SSLConnectionClosedByUserException e) {
380 // hack: if we have an SSL connection failure,
381 // we don't want to reconnect
382 req.mFailCount = RETRY_REQUEST_LIMIT;
383 // no error message
384 return false;
385 } catch (SSLHandshakeException e) {
386 // hack: if we have an SSL connection failure,
387 // we don't want to reconnect
388 req.mFailCount = RETRY_REQUEST_LIMIT;
389 if (HttpLog.LOGV) HttpLog.v(
390 "SSL exception performing handshake");
391 error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE;
392 exception = e;
393 } catch (IOException e) {
394 error = EventHandler.ERROR_CONNECT;
395 exception = e;
396 }
397
398 if (HttpLog.LOGV) {
399 long now2 = SystemClock.uptimeMillis();
400 HttpLog.v("Connection.openHttpConnection() " +
401 (now2 - now) + " " + mHost);
402 }
403
404 if (error == EventHandler.OK) {
405 return true;
406 } else {
Patrick Scottaaebc862009-09-16 09:59:12 -0400407 if (req.mFailCount < RETRY_REQUEST_LIMIT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 // requeue
409 mRequestFeeder.requeueRequest(req);
410 req.mFailCount++;
411 } else {
412 httpFailure(req, error, exception);
413 }
414 return error == EventHandler.OK;
415 }
416 }
417
418 /**
419 * Helper. Calls the mEventHandler's error() method only if
420 * request failed permanently. Increments mFailcount on failure.
421 *
422 * Increments failcount only if the network is believed to be
423 * connected
424 *
425 * @return true if request can be retried (less than
426 * RETRY_REQUEST_LIMIT failures have occurred).
427 */
428 private boolean httpFailure(Request req, int errorId, Exception e) {
429 boolean ret = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430
431 // e.printStackTrace();
432 if (HttpLog.LOGV) HttpLog.v(
433 "httpFailure() ******* " + e + " count " + req.mFailCount +
Patrick Scottaaebc862009-09-16 09:59:12 -0400434 " " + mHost + " " + req.getUri());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800435
Patrick Scottaaebc862009-09-16 09:59:12 -0400436 if (++req.mFailCount >= RETRY_REQUEST_LIMIT) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 ret = false;
438 String error;
439 if (errorId < 0) {
Iain Merrick83d4a232010-11-12 14:11:16 +0000440 error = ErrorStrings.getString(errorId, mContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 } else {
442 Throwable cause = e.getCause();
443 error = cause != null ? cause.toString() : e.getMessage();
444 }
445 req.mEventHandler.error(errorId, error);
446 req.complete();
447 }
448
449 closeConnection();
450 mHttpContext.removeAttribute(HTTP_CONNECTION);
451
452 return ret;
453 }
454
455 HttpContext getHttpContext() {
456 return mHttpContext;
457 }
458
459 /**
460 * Use same logic as ConnectionReuseStrategy
461 * @see ConnectionReuseStrategy
462 */
463 private boolean keepAlive(HttpEntity entity,
464 ProtocolVersion ver, int connType, final HttpContext context) {
465 org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection)
466 context.getAttribute(ExecutionContext.HTTP_CONNECTION);
467
468 if (conn != null && !conn.isOpen())
469 return false;
470 // do NOT check for stale connection, that is an expensive operation
471
472 if (entity != null) {
473 if (entity.getContentLength() < 0) {
474 if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) {
475 // if the content length is not known and is not chunk
476 // encoded, the connection cannot be reused
477 return false;
478 }
479 }
480 }
481 // Check for 'Connection' directive
482 if (connType == Headers.CONN_CLOSE) {
483 return false;
484 } else if (connType == Headers.CONN_KEEP_ALIVE) {
485 return true;
486 }
487 // Resorting to protocol version default close connection policy
488 return !ver.lessEquals(HttpVersion.HTTP_1_0);
489 }
490
491 void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) {
492 mCanPersist = keepAlive(entity, ver, connType, mHttpContext);
493 }
494
495 void setCanPersist(boolean canPersist) {
496 mCanPersist = canPersist;
497 }
498
499 boolean getCanPersist() {
500 return mCanPersist;
501 }
502
503 /** typically http or https... set by subclass */
504 abstract String getScheme();
505 abstract void closeConnection();
506 abstract AndroidHttpClientConnection openConnection(Request req) throws IOException;
507
508 /**
509 * Prints request queue to log, for debugging.
510 * returns request count
511 */
512 public synchronized String toString() {
513 return mHost.toString();
514 }
515
516 byte[] getBuf() {
517 if (mBuf == null) mBuf = new byte[8192];
518 return mBuf;
519 }
520
521}