1 /* 2 * Copyright (C) 2008 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 org.apache.http.HttpConnection; 20 import org.apache.http.HttpClientConnection; 21 import org.apache.http.HttpConnectionMetrics; 22 import org.apache.http.HttpEntity; 23 import org.apache.http.HttpEntityEnclosingRequest; 24 import org.apache.http.HttpException; 25 import org.apache.http.HttpInetConnection; 26 import org.apache.http.HttpRequest; 27 import org.apache.http.HttpResponse; 28 import org.apache.http.NoHttpResponseException; 29 import org.apache.http.StatusLine; 30 import org.apache.http.entity.BasicHttpEntity; 31 import org.apache.http.entity.ContentLengthStrategy; 32 import org.apache.http.impl.HttpConnectionMetricsImpl; 33 import org.apache.http.impl.entity.EntitySerializer; 34 import org.apache.http.impl.entity.StrictContentLengthStrategy; 35 import org.apache.http.impl.io.ChunkedInputStream; 36 import org.apache.http.impl.io.ContentLengthInputStream; 37 import org.apache.http.impl.io.HttpRequestWriter; 38 import org.apache.http.impl.io.IdentityInputStream; 39 import org.apache.http.impl.io.SocketInputBuffer; 40 import org.apache.http.impl.io.SocketOutputBuffer; 41 import org.apache.http.io.HttpMessageWriter; 42 import org.apache.http.io.SessionInputBuffer; 43 import org.apache.http.io.SessionOutputBuffer; 44 import org.apache.http.message.BasicLineParser; 45 import org.apache.http.message.ParserCursor; 46 import org.apache.http.params.CoreConnectionPNames; 47 import org.apache.http.params.HttpConnectionParams; 48 import org.apache.http.params.HttpParams; 49 import org.apache.http.ParseException; 50 import org.apache.http.util.CharArrayBuffer; 51 52 import java.io.IOException; 53 import java.net.InetAddress; 54 import java.net.Socket; 55 import java.net.SocketException; 56 57 /** 58 * A alternate class for (@link DefaultHttpClientConnection). 59 * It has better performance than DefaultHttpClientConnection 60 * 61 * {@hide} 62 */ 63 public class AndroidHttpClientConnection 64 implements HttpInetConnection, HttpConnection { 65 66 private SessionInputBuffer inbuffer = null; 67 private SessionOutputBuffer outbuffer = null; 68 private int maxHeaderCount; 69 // store CoreConnectionPNames.MAX_LINE_LENGTH for performance 70 private int maxLineLength; 71 72 private final EntitySerializer entityserializer; 73 74 private HttpMessageWriter requestWriter = null; 75 private HttpConnectionMetricsImpl metrics = null; 76 private volatile boolean open; 77 private Socket socket = null; 78 AndroidHttpClientConnection()79 public AndroidHttpClientConnection() { 80 this.entityserializer = new EntitySerializer( 81 new StrictContentLengthStrategy()); 82 } 83 84 /** 85 * Bind socket and set HttpParams to AndroidHttpClientConnection 86 * @param socket outgoing socket 87 * @param params HttpParams 88 * @throws IOException 89 */ bind( final Socket socket, final HttpParams params)90 public void bind( 91 final Socket socket, 92 final HttpParams params) throws IOException { 93 if (socket == null) { 94 throw new IllegalArgumentException("Socket may not be null"); 95 } 96 if (params == null) { 97 throw new IllegalArgumentException("HTTP parameters may not be null"); 98 } 99 assertNotOpen(); 100 socket.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); 101 socket.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); 102 103 int linger = HttpConnectionParams.getLinger(params); 104 if (linger >= 0) { 105 socket.setSoLinger(linger > 0, linger); 106 } 107 this.socket = socket; 108 109 int buffersize = HttpConnectionParams.getSocketBufferSize(params); 110 this.inbuffer = new SocketInputBuffer(socket, buffersize, params); 111 this.outbuffer = new SocketOutputBuffer(socket, buffersize, params); 112 113 maxHeaderCount = params.getIntParameter( 114 CoreConnectionPNames.MAX_HEADER_COUNT, -1); 115 maxLineLength = params.getIntParameter( 116 CoreConnectionPNames.MAX_LINE_LENGTH, -1); 117 118 this.requestWriter = new HttpRequestWriter(outbuffer, null, params); 119 120 this.metrics = new HttpConnectionMetricsImpl( 121 inbuffer.getMetrics(), 122 outbuffer.getMetrics()); 123 124 this.open = true; 125 } 126 127 @Override toString()128 public String toString() { 129 StringBuilder buffer = new StringBuilder(); 130 buffer.append(getClass().getSimpleName()).append("["); 131 if (isOpen()) { 132 buffer.append(getRemotePort()); 133 } else { 134 buffer.append("closed"); 135 } 136 buffer.append("]"); 137 return buffer.toString(); 138 } 139 140 assertNotOpen()141 private void assertNotOpen() { 142 if (this.open) { 143 throw new IllegalStateException("Connection is already open"); 144 } 145 } 146 assertOpen()147 private void assertOpen() { 148 if (!this.open) { 149 throw new IllegalStateException("Connection is not open"); 150 } 151 } 152 isOpen()153 public boolean isOpen() { 154 // to make this method useful, we want to check if the socket is connected 155 return (this.open && this.socket != null && this.socket.isConnected()); 156 } 157 getLocalAddress()158 public InetAddress getLocalAddress() { 159 if (this.socket != null) { 160 return this.socket.getLocalAddress(); 161 } else { 162 return null; 163 } 164 } 165 getLocalPort()166 public int getLocalPort() { 167 if (this.socket != null) { 168 return this.socket.getLocalPort(); 169 } else { 170 return -1; 171 } 172 } 173 getRemoteAddress()174 public InetAddress getRemoteAddress() { 175 if (this.socket != null) { 176 return this.socket.getInetAddress(); 177 } else { 178 return null; 179 } 180 } 181 getRemotePort()182 public int getRemotePort() { 183 if (this.socket != null) { 184 return this.socket.getPort(); 185 } else { 186 return -1; 187 } 188 } 189 setSocketTimeout(int timeout)190 public void setSocketTimeout(int timeout) { 191 assertOpen(); 192 if (this.socket != null) { 193 try { 194 this.socket.setSoTimeout(timeout); 195 } catch (SocketException ignore) { 196 // It is not quite clear from the original documentation if there are any 197 // other legitimate cases for a socket exception to be thrown when setting 198 // SO_TIMEOUT besides the socket being already closed 199 } 200 } 201 } 202 getSocketTimeout()203 public int getSocketTimeout() { 204 if (this.socket != null) { 205 try { 206 return this.socket.getSoTimeout(); 207 } catch (SocketException ignore) { 208 return -1; 209 } 210 } else { 211 return -1; 212 } 213 } 214 shutdown()215 public void shutdown() throws IOException { 216 this.open = false; 217 Socket tmpsocket = this.socket; 218 if (tmpsocket != null) { 219 tmpsocket.close(); 220 } 221 } 222 close()223 public void close() throws IOException { 224 if (!this.open) { 225 return; 226 } 227 this.open = false; 228 doFlush(); 229 try { 230 try { 231 this.socket.shutdownOutput(); 232 } catch (IOException ignore) { 233 } 234 try { 235 this.socket.shutdownInput(); 236 } catch (IOException ignore) { 237 } 238 } catch (UnsupportedOperationException ignore) { 239 // if one isn't supported, the other one isn't either 240 } 241 this.socket.close(); 242 } 243 244 /** 245 * Sends the request line and all headers over the connection. 246 * @param request the request whose headers to send. 247 * @throws HttpException 248 * @throws IOException 249 */ sendRequestHeader(final HttpRequest request)250 public void sendRequestHeader(final HttpRequest request) 251 throws HttpException, IOException { 252 if (request == null) { 253 throw new IllegalArgumentException("HTTP request may not be null"); 254 } 255 assertOpen(); 256 this.requestWriter.write(request); 257 this.metrics.incrementRequestCount(); 258 } 259 260 /** 261 * Sends the request entity over the connection. 262 * @param request the request whose entity to send. 263 * @throws HttpException 264 * @throws IOException 265 */ sendRequestEntity(final HttpEntityEnclosingRequest request)266 public void sendRequestEntity(final HttpEntityEnclosingRequest request) 267 throws HttpException, IOException { 268 if (request == null) { 269 throw new IllegalArgumentException("HTTP request may not be null"); 270 } 271 assertOpen(); 272 if (request.getEntity() == null) { 273 return; 274 } 275 this.entityserializer.serialize( 276 this.outbuffer, 277 request, 278 request.getEntity()); 279 } 280 doFlush()281 protected void doFlush() throws IOException { 282 this.outbuffer.flush(); 283 } 284 flush()285 public void flush() throws IOException { 286 assertOpen(); 287 doFlush(); 288 } 289 290 /** 291 * Parses the response headers and adds them to the 292 * given {@code headers} object, and returns the response StatusLine 293 * @param headers store parsed header to headers. 294 * @throws IOException 295 * @return StatusLine 296 * @see HttpClientConnection#receiveResponseHeader() 297 */ parseResponseHeader(Headers headers)298 public StatusLine parseResponseHeader(Headers headers) 299 throws IOException, ParseException { 300 assertOpen(); 301 302 CharArrayBuffer current = new CharArrayBuffer(64); 303 304 if (inbuffer.readLine(current) == -1) { 305 throw new NoHttpResponseException("The target server failed to respond"); 306 } 307 308 // Create the status line from the status string 309 StatusLine statusline = BasicLineParser.DEFAULT.parseStatusLine( 310 current, new ParserCursor(0, current.length())); 311 312 if (HttpLog.LOGV) HttpLog.v("read: " + statusline); 313 int statusCode = statusline.getStatusCode(); 314 315 // Parse header body 316 CharArrayBuffer previous = null; 317 int headerNumber = 0; 318 while(true) { 319 if (current == null) { 320 current = new CharArrayBuffer(64); 321 } else { 322 // This must be he buffer used to parse the status 323 current.clear(); 324 } 325 int l = inbuffer.readLine(current); 326 if (l == -1 || current.length() < 1) { 327 break; 328 } 329 // Parse the header name and value 330 // Check for folded headers first 331 // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2 332 // discussion on folded headers 333 char first = current.charAt(0); 334 if ((first == ' ' || first == '\t') && previous != null) { 335 // we have continuation folded header 336 // so append value 337 int start = 0; 338 int length = current.length(); 339 while (start < length) { 340 char ch = current.charAt(start); 341 if (ch != ' ' && ch != '\t') { 342 break; 343 } 344 start++; 345 } 346 if (maxLineLength > 0 && 347 previous.length() + 1 + current.length() - start > 348 maxLineLength) { 349 throw new IOException("Maximum line length limit exceeded"); 350 } 351 previous.append(' '); 352 previous.append(current, start, current.length() - start); 353 } else { 354 if (previous != null) { 355 headers.parseHeader(previous); 356 } 357 headerNumber++; 358 previous = current; 359 current = null; 360 } 361 if (maxHeaderCount > 0 && headerNumber >= maxHeaderCount) { 362 throw new IOException("Maximum header count exceeded"); 363 } 364 } 365 366 if (previous != null) { 367 headers.parseHeader(previous); 368 } 369 370 if (statusCode >= 200) { 371 this.metrics.incrementResponseCount(); 372 } 373 return statusline; 374 } 375 376 /** 377 * Return the next response entity. 378 * @param headers contains values for parsing entity 379 * @see HttpClientConnection#receiveResponseEntity(HttpResponse response) 380 */ receiveResponseEntity(final Headers headers)381 public HttpEntity receiveResponseEntity(final Headers headers) { 382 assertOpen(); 383 BasicHttpEntity entity = new BasicHttpEntity(); 384 385 long len = determineLength(headers); 386 if (len == ContentLengthStrategy.CHUNKED) { 387 entity.setChunked(true); 388 entity.setContentLength(-1); 389 entity.setContent(new ChunkedInputStream(inbuffer)); 390 } else if (len == ContentLengthStrategy.IDENTITY) { 391 entity.setChunked(false); 392 entity.setContentLength(-1); 393 entity.setContent(new IdentityInputStream(inbuffer)); 394 } else { 395 entity.setChunked(false); 396 entity.setContentLength(len); 397 entity.setContent(new ContentLengthInputStream(inbuffer, len)); 398 } 399 400 String contentTypeHeader = headers.getContentType(); 401 if (contentTypeHeader != null) { 402 entity.setContentType(contentTypeHeader); 403 } 404 String contentEncodingHeader = headers.getContentEncoding(); 405 if (contentEncodingHeader != null) { 406 entity.setContentEncoding(contentEncodingHeader); 407 } 408 409 return entity; 410 } 411 determineLength(final Headers headers)412 private long determineLength(final Headers headers) { 413 long transferEncoding = headers.getTransferEncoding(); 414 // We use Transfer-Encoding if present and ignore Content-Length. 415 // RFC2616, 4.4 item number 3 416 if (transferEncoding < Headers.NO_TRANSFER_ENCODING) { 417 return transferEncoding; 418 } else { 419 long contentlen = headers.getContentLength(); 420 if (contentlen > Headers.NO_CONTENT_LENGTH) { 421 return contentlen; 422 } else { 423 return ContentLengthStrategy.IDENTITY; 424 } 425 } 426 } 427 428 /** 429 * Checks whether this connection has gone down. 430 * Network connections may get closed during some time of inactivity 431 * for several reasons. The next time a read is attempted on such a 432 * connection it will throw an IOException. 433 * This method tries to alleviate this inconvenience by trying to 434 * find out if a connection is still usable. Implementations may do 435 * that by attempting a read with a very small timeout. Thus this 436 * method may block for a small amount of time before returning a result. 437 * It is therefore an <i>expensive</i> operation. 438 * 439 * @return <code>true</code> if attempts to use this connection are 440 * likely to succeed, or <code>false</code> if they are likely 441 * to fail and this connection should be closed 442 */ isStale()443 public boolean isStale() { 444 assertOpen(); 445 try { 446 this.inbuffer.isDataAvailable(1); 447 return false; 448 } catch (IOException ex) { 449 return true; 450 } 451 } 452 453 /** 454 * Returns a collection of connection metrcis 455 * @return HttpConnectionMetrics 456 */ getMetrics()457 public HttpConnectionMetrics getMetrics() { 458 return this.metrics; 459 } 460 } 461