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