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