Connection.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
1/* 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 97 RequestQueue.ConnectionManager mConnectionManager; 98 RequestFeeder mRequestFeeder; 99 100 /** 101 * Buffer for feeding response blocks to webkit. One block per 102 * connection reduces memory churn. 103 */ 104 private byte[] mBuf; 105 106 protected Connection(Context context, HttpHost host, 107 RequestQueue.ConnectionManager connectionManager, 108 RequestFeeder requestFeeder) { 109 mContext = context; 110 mHost = host; 111 mConnectionManager = connectionManager; 112 mRequestFeeder = requestFeeder; 113 114 mCanPersist = false; 115 mHttpContext = new BasicHttpContext(null); 116 } 117 118 HttpHost getHost() { 119 return mHost; 120 } 121 122 /** 123 * connection factory: returns an HTTP or HTTPS connection as 124 * necessary 125 */ 126 static Connection getConnection( 127 Context context, HttpHost host, 128 RequestQueue.ConnectionManager connectionManager, 129 RequestFeeder requestFeeder) { 130 131 if (host.getSchemeName().equals("http")) { 132 return new HttpConnection(context, host, connectionManager, 133 requestFeeder); 134 } 135 136 // Otherwise, default to https 137 return new HttpsConnection(context, host, connectionManager, 138 requestFeeder); 139 } 140 141 /** 142 * @return The server SSL certificate associated with this 143 * connection (null if the connection is not secure) 144 */ 145 /* package */ SslCertificate getCertificate() { 146 return mCertificate; 147 } 148 149 /** 150 * Close current network connection 151 * Note: this runs in non-network thread 152 */ 153 void cancel() { 154 mActive = STATE_CANCEL_REQUESTED; 155 closeConnection(); 156 if (HttpLog.LOGV) HttpLog.v( 157 "Connection.cancel(): connection closed " + mHost); 158 } 159 160 /** 161 * Process requests in queue 162 * pipelines requests 163 */ 164 void processRequests(Request firstRequest) { 165 Request req = null; 166 boolean empty; 167 int error = EventHandler.OK; 168 Exception exception = null; 169 170 LinkedList<Request> pipe = new LinkedList<Request>(); 171 172 int minPipe = MIN_PIPE, maxPipe = MAX_PIPE; 173 int state = SEND; 174 175 while (state != DONE) { 176 if (HttpLog.LOGV) HttpLog.v( 177 states[state] + " pipe " + pipe.size()); 178 179 /* If a request was cancelled, give other cancel requests 180 some time to go through so we don't uselessly restart 181 connections */ 182 if (mActive == STATE_CANCEL_REQUESTED) { 183 try { 184 Thread.sleep(100); 185 } catch (InterruptedException x) { /* ignore */ } 186 mActive = STATE_NORMAL; 187 } 188 189 switch (state) { 190 case SEND: { 191 if (pipe.size() == maxPipe) { 192 state = READ; 193 break; 194 } 195 /* get a request */ 196 if (firstRequest == null) { 197 req = mRequestFeeder.getRequest(mHost); 198 } else { 199 req = firstRequest; 200 firstRequest = null; 201 } 202 if (req == null) { 203 state = DRAIN; 204 break; 205 } 206 req.setConnection(this); 207 208 /* Don't work on cancelled requests. */ 209 if (req.mCancelled) { 210 if (HttpLog.LOGV) HttpLog.v( 211 "processRequests(): skipping cancelled request " 212 + req); 213 req.complete(); 214 break; 215 } 216 217 if (mHttpClientConnection == null || 218 !mHttpClientConnection.isOpen()) { 219 /* If this call fails, the address is bad or 220 the net is down. Punt for now. 221 222 FIXME: blow out entire queue here on 223 connection failure if net up? */ 224 225 if (!openHttpConnection(req)) { 226 state = DONE; 227 break; 228 } 229 } 230 231 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; 254 state = (clearPipe(pipe) || 255 !mConnectionManager.isNetworkConnected()) ? 256 DONE : SEND; 257 minPipe = maxPipe = 1; 258 break; 259 } 260 261 pipe.addLast(req); 262 if (!mCanPersist) state = READ; 263 break; 264 265 } 266 case DRAIN: 267 case READ: { 268 empty = !mRequestFeeder.haveRequest(mHost); 269 int pipeSize = pipe.size(); 270 if (state != DRAIN && pipeSize < minPipe && 271 !empty && mCanPersist) { 272 state = SEND; 273 break; 274 } else if (pipeSize == 0) { 275 /* Done if no other work to do */ 276 state = empty ? DONE : SEND; 277 break; 278 } 279 280 req = (Request)pipe.removeFirst(); 281 if (HttpLog.LOGV) HttpLog.v( 282 "processRequests() reading " + req); 283 284 try { 285 req.readResponse(mHttpClientConnection); 286 } catch (ParseException e) { 287 exception = e; 288 error = EventHandler.ERROR_IO; 289 } catch (IOException e) { 290 exception = e; 291 error = EventHandler.ERROR_IO; 292 } catch (IllegalStateException e) { 293 exception = e; 294 error = EventHandler.ERROR_IO; 295 } 296 if (exception != null) { 297 if (httpFailure(req, error, exception) && 298 !req.mCancelled) { 299 /* retry request if not permanent failure 300 or cancelled */ 301 req.reset(); 302 pipe.addFirst(req); 303 } 304 exception = null; 305 mCanPersist = false; 306 } 307 if (!mCanPersist) { 308 if (HttpLog.LOGV) HttpLog.v( 309 "processRequests(): no persist, closing " + 310 mHost); 311 312 closeConnection(); 313 314 mHttpContext.removeAttribute(HTTP_CONNECTION); 315 clearPipe(pipe); 316 minPipe = maxPipe = 1; 317 /* If network active continue to service this queue */ 318 state = mConnectionManager.isNetworkConnected() ? 319 SEND : DONE; 320 } 321 break; 322 } 323 } 324 } 325 } 326 327 /** 328 * After a send/receive failure, any pipelined requests must be 329 * cleared back to the mRequest queue 330 * @return true if mRequests is empty after pipe cleared 331 */ 332 private boolean clearPipe(LinkedList<Request> pipe) { 333 boolean empty = true; 334 if (HttpLog.LOGV) HttpLog.v( 335 "Connection.clearPipe(): clearing pipe " + pipe.size()); 336 synchronized (mRequestFeeder) { 337 Request tReq; 338 while (!pipe.isEmpty()) { 339 tReq = (Request)pipe.removeLast(); 340 if (HttpLog.LOGV) HttpLog.v( 341 "clearPipe() adding back " + mHost + " " + tReq); 342 mRequestFeeder.requeueRequest(tReq); 343 empty = false; 344 } 345 if (empty) empty = mRequestFeeder.haveRequest(mHost); 346 } 347 return empty; 348 } 349 350 /** 351 * @return true on success 352 */ 353 private boolean openHttpConnection(Request req) { 354 355 long now = SystemClock.uptimeMillis(); 356 int error = EventHandler.OK; 357 Exception exception = null; 358 359 try { 360 // reset the certificate to null before opening a connection 361 mCertificate = null; 362 mHttpClientConnection = openConnection(req); 363 if (mHttpClientConnection != null) { 364 mHttpClientConnection.setSocketTimeout(SOCKET_TIMEOUT); 365 mHttpContext.setAttribute(HTTP_CONNECTION, 366 mHttpClientConnection); 367 } else { 368 // we tried to do SSL tunneling, failed, 369 // and need to drop the request; 370 // we have already informed the handler 371 req.mFailCount = RETRY_REQUEST_LIMIT; 372 return false; 373 } 374 } catch (UnknownHostException e) { 375 if (HttpLog.LOGV) HttpLog.v("Failed to open connection"); 376 error = EventHandler.ERROR_LOOKUP; 377 exception = e; 378 } catch (IllegalArgumentException e) { 379 if (HttpLog.LOGV) HttpLog.v("Illegal argument exception"); 380 error = EventHandler.ERROR_CONNECT; 381 req.mFailCount = RETRY_REQUEST_LIMIT; 382 exception = e; 383 } catch (SSLConnectionClosedByUserException e) { 384 // hack: if we have an SSL connection failure, 385 // we don't want to reconnect 386 req.mFailCount = RETRY_REQUEST_LIMIT; 387 // no error message 388 return false; 389 } catch (SSLHandshakeException e) { 390 // hack: if we have an SSL connection failure, 391 // we don't want to reconnect 392 req.mFailCount = RETRY_REQUEST_LIMIT; 393 if (HttpLog.LOGV) HttpLog.v( 394 "SSL exception performing handshake"); 395 error = EventHandler.ERROR_FAILED_SSL_HANDSHAKE; 396 exception = e; 397 } catch (IOException e) { 398 error = EventHandler.ERROR_CONNECT; 399 exception = e; 400 } 401 402 if (HttpLog.LOGV) { 403 long now2 = SystemClock.uptimeMillis(); 404 HttpLog.v("Connection.openHttpConnection() " + 405 (now2 - now) + " " + mHost); 406 } 407 408 if (error == EventHandler.OK) { 409 return true; 410 } else { 411 if (mConnectionManager.isNetworkConnected() == false || 412 req.mFailCount < RETRY_REQUEST_LIMIT) { 413 // requeue 414 mRequestFeeder.requeueRequest(req); 415 req.mFailCount++; 416 } else { 417 httpFailure(req, error, exception); 418 } 419 return error == EventHandler.OK; 420 } 421 } 422 423 /** 424 * Helper. Calls the mEventHandler's error() method only if 425 * request failed permanently. Increments mFailcount on failure. 426 * 427 * Increments failcount only if the network is believed to be 428 * connected 429 * 430 * @return true if request can be retried (less than 431 * RETRY_REQUEST_LIMIT failures have occurred). 432 */ 433 private boolean httpFailure(Request req, int errorId, Exception e) { 434 boolean ret = true; 435 boolean networkConnected = mConnectionManager.isNetworkConnected(); 436 437 // e.printStackTrace(); 438 if (HttpLog.LOGV) HttpLog.v( 439 "httpFailure() ******* " + e + " count " + req.mFailCount + 440 " networkConnected " + networkConnected + " " + mHost + " " + req.getUri()); 441 442 if (networkConnected && ++req.mFailCount >= RETRY_REQUEST_LIMIT) { 443 ret = false; 444 String error; 445 if (errorId < 0) { 446 error = mContext.getText( 447 EventHandler.errorStringResources[-errorId]).toString(); 448 } else { 449 Throwable cause = e.getCause(); 450 error = cause != null ? cause.toString() : e.getMessage(); 451 } 452 req.mEventHandler.error(errorId, error); 453 req.complete(); 454 } 455 456 closeConnection(); 457 mHttpContext.removeAttribute(HTTP_CONNECTION); 458 459 return ret; 460 } 461 462 HttpContext getHttpContext() { 463 return mHttpContext; 464 } 465 466 /** 467 * Use same logic as ConnectionReuseStrategy 468 * @see ConnectionReuseStrategy 469 */ 470 private boolean keepAlive(HttpEntity entity, 471 ProtocolVersion ver, int connType, final HttpContext context) { 472 org.apache.http.HttpConnection conn = (org.apache.http.HttpConnection) 473 context.getAttribute(ExecutionContext.HTTP_CONNECTION); 474 475 if (conn != null && !conn.isOpen()) 476 return false; 477 // do NOT check for stale connection, that is an expensive operation 478 479 if (entity != null) { 480 if (entity.getContentLength() < 0) { 481 if (!entity.isChunked() || ver.lessEquals(HttpVersion.HTTP_1_0)) { 482 // if the content length is not known and is not chunk 483 // encoded, the connection cannot be reused 484 return false; 485 } 486 } 487 } 488 // Check for 'Connection' directive 489 if (connType == Headers.CONN_CLOSE) { 490 return false; 491 } else if (connType == Headers.CONN_KEEP_ALIVE) { 492 return true; 493 } 494 // Resorting to protocol version default close connection policy 495 return !ver.lessEquals(HttpVersion.HTTP_1_0); 496 } 497 498 void setCanPersist(HttpEntity entity, ProtocolVersion ver, int connType) { 499 mCanPersist = keepAlive(entity, ver, connType, mHttpContext); 500 } 501 502 void setCanPersist(boolean canPersist) { 503 mCanPersist = canPersist; 504 } 505 506 boolean getCanPersist() { 507 return mCanPersist; 508 } 509 510 /** typically http or https... set by subclass */ 511 abstract String getScheme(); 512 abstract void closeConnection(); 513 abstract AndroidHttpClientConnection openConnection(Request req) throws IOException; 514 515 /** 516 * Prints request queue to log, for debugging. 517 * returns request count 518 */ 519 public synchronized String toString() { 520 return mHost.toString(); 521 } 522 523 byte[] getBuf() { 524 if (mBuf == null) mBuf = new byte[8192]; 525 return mBuf; 526 } 527 528} 529