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