• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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