KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > go > trove > net > HttpClient


1 /* ====================================================================
2  * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
3  * ====================================================================
4  * The Tea Software License, Version 1.1
5  *
6  * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Walt Disney Internet Group (http://opensource.go.com/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact opensource@dig.com.
31  *
32  * 5. Products derived from this software may not be called "Tea",
33  * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34  * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35  * written permission of the Walt Disney Internet Group.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
41  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  * ====================================================================
49  *
50  * For more information about Tea, please see http://opensource.go.com/.
51  */

52
53 package com.go.trove.net;
54
55 import java.net.*;
56 import java.io.*;
57 import java.util.*;
58 import com.go.trove.io.*;
59
60 /******************************************************************************
61  *
62  * @author Brian S O'Neill
63  * @version
64  * <!--$$Revision:--> 12 <!-- $-->, <!--$$JustDate:--> 01/06/14 <!-- $-->
65  */

66 public class HttpClient {
67     private final SocketFactory mFactory;
68     private final int mReadTimeout;
69
70     private String JavaDoc mMethod = "GET";
71     private String JavaDoc mURI = "";
72     private String JavaDoc mProtocol = "HTTP/1.0";
73     private HttpHeaderMap mHeaders;
74
75     private Object JavaDoc mSession;
76     
77     /**
78      * Constructs a HttpClient with a read timeout that matches the given
79      * factory's connect timeout.
80      *
81      * @param factory source of socket connections
82      */

83     public HttpClient(SocketFactory factory) {
84         this(factory, factory.getDefaultTimeout());
85     }
86
87     /**
88      * @param factory source of socket connections
89      * @param readTimeout timeout on socket read operations before throwing a
90      * InterruptedIOException
91      */

92     public HttpClient(SocketFactory factory, long readTimeout) {
93         mFactory = factory;
94         if (readTimeout == 0) {
95             mReadTimeout = 1;
96         }
97         else if (readTimeout < 0) {
98             mReadTimeout = 0;
99         }
100         else if (readTimeout > Integer.MAX_VALUE) {
101             mReadTimeout = Integer.MAX_VALUE;
102         }
103         else {
104             mReadTimeout = (int)readTimeout;
105         }
106     }
107
108     /**
109      * Set the HTTP request method, which defaults to "GET".
110      *
111      * @return 'this', so that addtional calls may be chained together
112      */

113     public HttpClient setMethod(String JavaDoc method) {
114         mMethod = method;
115         return this;
116     }
117
118     /**
119      * Set the URI to request, which can include a query string.
120      *
121      * @return 'this', so that addtional calls may be chained together
122      */

123     public HttpClient setURI(String JavaDoc uri) {
124         mURI = uri;
125         return this;
126     }
127
128     /**
129      * Set the HTTP protocol string, which defaults to "HTTP/1.0".
130      *
131      * @return 'this', so that addtional calls may be chained together
132      */

133     public HttpClient setProtocol(String JavaDoc protocol) {
134         mProtocol = protocol;
135         return this;
136     }
137
138     /**
139      * Set a header name-value pair to the request.
140      *
141      * @return 'this', so that addtional calls may be chained together
142      */

143     public HttpClient setHeader(String JavaDoc name, Object JavaDoc value) {
144         if (mHeaders == null) {
145             mHeaders = new HttpHeaderMap();
146         }
147         mHeaders.put(name, value);
148         return this;
149     }
150
151     /**
152      * Add a header name-value pair to the request in order for multiple values
153      * to be specified.
154      *
155      * @return 'this', so that addtional calls may be chained together
156      */

157     public HttpClient addHeader(String JavaDoc name, Object JavaDoc value) {
158         if (mHeaders == null) {
159             mHeaders = new HttpHeaderMap();
160         }
161         mHeaders.add(name, value);
162         return this;
163     }
164
165     /**
166      * Set all the headers for this request, replacing any existing headers.
167      * If any more headers are added to this request, they will be stored in
168      * the given HttpHeaderMap.
169      *
170      * @return 'this', so that addtional calls may be chained together
171      */

172     public HttpClient setHeaders(HttpHeaderMap headers) {
173         mHeaders = headers;
174         return this;
175     }
176     
177     /**
178      * Convenience method for setting the "Connection" header to "Keep-Alive"
179      * or "Close".
180      *
181      * @param b true for persistent connection
182      * @return 'this', so that addtional calls may be chained together
183      */

184     public HttpClient setPersistent(boolean b) {
185         if (b) {
186             setHeader("Connection", "Keep-Alive");
187         }
188         else {
189             setHeader("Connection", "Close");
190         }
191         return this;
192     }
193
194     /**
195      * Convenience method for preparing a post to the server. This method sets
196      * the method to "POST", sets the "Content-Length" header, and sets the
197      * "Content-Type" header to "application/x-www-form-urlencoded". When
198      * calling getResponse, PostData must be provided.
199      *
200      * @param contentLength number of bytes to be posted
201      * @return 'this', so that addtional calls may be chained together
202      */

203     public HttpClient preparePost(int contentLength) {
204         setMethod("POST");
205         setHeader("Content-Type", "application/x-www-form-urlencoded");
206         setHeader("Content-Length", new Integer JavaDoc(contentLength));
207         return this;
208     }
209
210     /**
211      * Optionally specify a session for getting connections. If SocketFactory
212      * is distributed, then session helps to ensure the same server is routed
213      * to on multiple requests.
214      *
215      * @param session Object whose hashcode might be used to select a specific
216      * connection if factory is distributed. If null, then no session is used.
217      * @return 'this', so that addtional calls may be chained together
218      */

219     public HttpClient setSession(Object JavaDoc session) {
220         mSession = session;
221         return this;
222     }
223
224     /**
225      * Opens a connection, passes on the current request settings, and returns
226      * the server's response.
227      */

228     public Response getResponse() throws ConnectException, SocketException {
229         return getResponse(null);
230     }
231
232     /**
233      * Opens a connection, passes on the current request settings, and returns
234      * the server's response. The optional PostData parameter is used to
235      * supply post data to the server. The Content-Length header specifies
236      * how much data will be read from the PostData InputStream. If it is not
237      * specified, data will be read from the InputStream until EOF is reached.
238      *
239      * @param postData additional data to supply to the server, if request
240      * method is POST
241      */

242     public Response getResponse(PostData postData)
243         throws ConnectException, SocketException
244     {
245         CheckedSocket socket = mFactory.getSocket(mSession);
246         socket.setSoTimeout(mReadTimeout);
247
248         try {
249             CharToByteBuffer buffer = new FastCharToByteBuffer
250                 (new DefaultByteBuffer(), "8859_1");
251             buffer = new InternedCharToByteBuffer(buffer);
252             
253             buffer.append(mMethod);
254             buffer.append(' ');
255             buffer.append(mURI);
256             buffer.append(' ');
257             buffer.append(mProtocol);
258             buffer.append("\r\n");
259             if (mHeaders != null) {
260                 mHeaders.appendTo(buffer);
261             }
262             buffer.append("\r\n");
263
264             OutputStream out;
265             InputStream in;
266
267             out = new FastBufferedOutputStream(socket.getOutputStream());
268             buffer.writeTo(out);
269             if (postData != null) {
270                 writePostData(out, postData);
271             }
272             out.flush();
273             in = new FastBufferedInputStream(socket.getInputStream());
274             
275             // Read first line to see if connection is working.
276
char[] buf = new char[100];
277             String JavaDoc line;
278             try {
279                 line = HttpUtils.readLine(in, buf);
280             }
281             catch (IOException e) {
282                 line = null;
283             }
284
285             if (line == null) {
286                 // Try again with new connection.
287
try {
288                     socket.close();
289                 }
290                 catch (IOException e) {
291                 }
292
293                 socket = mFactory.createSocket(mSession);
294                 socket.setSoTimeout(mReadTimeout);
295
296                 out = new FastBufferedOutputStream(socket.getOutputStream());
297                 buffer.writeTo(out);
298                 if (postData != null) {
299                     writePostData(out, postData);
300                 }
301                 out.flush();
302                 in = new FastBufferedInputStream(socket.getInputStream());
303
304                 // Read first line again.
305
if ((line = HttpUtils.readLine(in, buf)) == null) {
306                     throw new ConnectException("No response from server");
307                 }
308             }
309
310             return new Response(socket, mMethod, in, buf, line);
311         }
312         catch (SocketException e) {
313             throw e;
314         }
315         catch (InterruptedIOException e) {
316             throw new ConnectException("Read timeout expired: " +
317                                        mReadTimeout + ", " + e);
318         }
319         catch (IOException e) {
320             throw new SocketException(e.toString());
321         }
322     }
323
324     private void writePostData(OutputStream out, PostData postData)
325         throws IOException
326     {
327         InputStream in = postData.getInputStream();
328         
329         int contentLength = -1;
330         if (mHeaders != null) {
331             Integer JavaDoc i = mHeaders.getInteger("Content-Length");
332             if (i != null) {
333                 contentLength = i.intValue();
334             }
335         }
336         
337         byte[] buf;
338         if (contentLength < 0 || contentLength > 4000) {
339             buf = new byte[4000];
340         }
341         else {
342             buf = new byte[contentLength];
343         }
344         
345         try {
346             int len;
347             if (contentLength < 0) {
348                 while ((len = in.read(buf)) > 0) {
349                     out.write(buf, 0, len);
350                 }
351             }
352             else {
353                 while (contentLength > 0) {
354                     len = buf.length;
355                     if (contentLength < len) {
356                         len = contentLength;
357                     }
358                     if ((len = in.read(buf, 0, len)) <= 0) {
359                         break;
360                     }
361                     out.write(buf, 0, len);
362                     contentLength -= len;
363                 }
364             }
365         }
366         finally {
367             in.close();
368         }
369     }
370
371     /**
372      * A factory for supplying data to be written to server in a POST request.
373      */

374     public static interface PostData {
375         /**
376          * Returns the actual data via an InputStream. If the client needs to
377          * reconnect to the server, this method may be called again. The
378          * InputStream is closed when all the post data has been read from it.
379          */

380         public InputStream getInputStream() throws IOException;
381     }
382
383     public class Response {
384         private final int mStatusCode;
385         private final String JavaDoc mStatusMessage;
386         private final HttpHeaderMap mHeaders;
387
388         private InputStream mIn;
389
390         Response(CheckedSocket socket, String JavaDoc method,
391                  InputStream in, char[] buf, String JavaDoc line) throws IOException
392         {
393             int statusCode = -1;
394             String JavaDoc statusMessage = "";
395
396             int space = line.indexOf(' ');
397             if (space > 0) {
398                 int nextSpace = line.indexOf(' ', space + 1);
399                 String JavaDoc sub;
400                 if (nextSpace < 0) {
401                     sub = line.substring(space + 1);
402                 }
403                 else {
404                     sub = line.substring(space + 1, nextSpace);
405                     statusMessage = line.substring(nextSpace + 1);
406                 }
407                 try {
408                     statusCode = Integer.parseInt(sub);
409                 }
410                 catch (NumberFormatException JavaDoc e) {
411                 }
412             }
413
414             if (statusCode < 0) {
415                 throw new ProtocolException("Invalid HTTP response: " + line);
416             }
417
418             mStatusCode = statusCode;
419             mStatusMessage = statusMessage;
420             mHeaders = new HttpHeaderMap();
421             mHeaders.readFrom(in, buf);
422
423             // Used for controlling persistent connections.
424
int contentLength;
425             if ("Keep-Alive".equalsIgnoreCase
426                 (mHeaders.getString("Connection"))) {
427
428                 if ("HEAD".equals(method)) {
429                     contentLength = 0;
430                 }
431                 else {
432                     Integer JavaDoc i = mHeaders.getInteger("Content-Length");
433                     if (i != null) {
434                         contentLength = i.intValue();
435                     }
436                     else {
437                         contentLength = -1;
438                     }
439                 }
440             }
441             else {
442                 contentLength = -1;
443             }
444
445             mIn = new ResponseInput(socket, in, contentLength);
446         }
447
448         /**
449          * Resturns the server's status code, 200 for OK, 404 for not found,
450          * etc.
451          */

452         public int getStatusCode() {
453             return mStatusCode;
454         }
455
456         /**
457          * Returns the server's status message accompanying the status code.
458          * This message is intended for humans only.
459          */

460         public String JavaDoc getStatusMessage() {
461             return mStatusMessage;
462         }
463
464         public HttpHeaderMap getHeaders() {
465             return mHeaders;
466         }
467
468         /**
469          * Returns an InputStream supplying the body of the response. When all
470          * of the response body has been read, the connection is either closed
471          * or recycled, depending on if all the criteria is met for supporting
472          * persistent connections. Further reads on the InputStream will
473          * return EOF.
474          */

475         public InputStream getInputStream() {
476             return mIn;
477         }
478     }
479
480     private class ResponseInput extends InputStream {
481         private CheckedSocket mSocket;
482         private InputStream mIn;
483         private int mContentLength;
484         
485         /**
486          * @param contentLength Used for supporting persistent connections. If
487          * negative, then close connection when EOF is read.
488          */

489         public ResponseInput(CheckedSocket socket,
490                              InputStream in, int contentLength)
491             throws IOException
492         {
493             mSocket = socket;
494             mIn = in;
495             if ((mContentLength = contentLength) == 0) {
496                 recycle();
497             }
498         }
499
500         public int read() throws IOException {
501             if (mContentLength == 0) {
502                 return -1;
503             }
504
505             int b = mIn.read();
506
507             if (b < 0) {
508                 close();
509             }
510             else if (mContentLength > 0) {
511                 if (--mContentLength == 0) {
512                     recycle();
513                 }
514             }
515
516             return b;
517         }
518
519         public int read(byte[] b) throws IOException {
520             return read(b, 0, b.length);
521         }
522
523         public int read(byte[] b, int off, int len) throws IOException {
524             if (mContentLength == 0) {
525                 return -1;
526             }
527
528             if (mContentLength < 0) {
529                 len = mIn.read(b, off, len);
530                 if (len < 0) {
531                     close();
532                 }
533                 else if (len == 0) {
534                     close();
535                     len = -1;
536                 }
537                 return len;
538             }
539
540             if (len > mContentLength) {
541                 len = mContentLength;
542             }
543             else if (len == 0) {
544                 return 0;
545             }
546
547             len = mIn.read(b, off, len);
548
549             if (len < 0) {
550                 close();
551             }
552             else if (len == 0) {
553                 close();
554                 len = -1;
555             }
556             else {
557                 if ((mContentLength -= len) == 0) {
558                     recycle();
559                 }
560             }
561
562             return len;
563         }
564
565         public long skip(long n) throws IOException {
566             if (mContentLength == 0) {
567                 return 0;
568             }
569
570             if (mContentLength < 0) {
571                 return mIn.skip(n);
572             }
573
574             if (n > mContentLength) {
575                 n = mContentLength;
576             }
577             else if (n == 0) {
578                 return 0;
579             }
580
581             n = mIn.skip(n);
582
583             if ((mContentLength -= n) == 0) {
584                 recycle();
585             }
586
587             return n;
588         }
589
590         public int available() throws IOException {
591             return mIn.available();
592         }
593
594         public void close() throws IOException {
595             if (mSocket != null) {
596                 mContentLength = 0;
597                 mSocket = null;
598                 mIn.close();
599             }
600         }
601
602         private void recycle() throws IOException {
603             if (mSocket != null) {
604                 if (mContentLength == 0) {
605                     CheckedSocket s = mSocket;
606                     mSocket = null;
607                     mFactory.recycleSocket(s);
608                 }
609                 else {
610                     mSocket = null;
611                     mIn.close();
612                 }
613             }
614         }
615     }
616 }
617
Popular Tags