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