KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > vfs > HttpStream


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  *
23  * Free Software Foundation, Inc.
24  * 59 Temple Place, Suite 330
25  * Boston, MA 02111-1307 USA
26  *
27  * @author Scott Ferguson
28  */

29
30 package com.caucho.vfs;
31
32 import com.caucho.util.Alarm;
33 import com.caucho.util.CharBuffer;
34 import com.caucho.util.Log;
35
36 import javax.net.ssl.SSLContext;
37 import javax.net.ssl.SSLSocketFactory;
38 import java.io.IOException JavaDoc;
39 import java.io.InputStream JavaDoc;
40 import java.io.OutputStream JavaDoc;
41 import java.net.ConnectException JavaDoc;
42 import java.net.Socket JavaDoc;
43 import java.net.SocketException JavaDoc;
44 import java.util.HashMap JavaDoc;
45 import java.util.Iterator JavaDoc;
46 import java.util.logging.Level JavaDoc;
47 import java.util.logging.Logger JavaDoc;
48
49 /**
50  * Underlying stream handling HTTP requests.
51  */

52 class HttpStream extends StreamImpl {
53   private static final Logger JavaDoc log = Log.open(HttpStream.class);
54   // reserved headers that should not be passed to the HTTP server
55
private static HashMap JavaDoc<String JavaDoc,String JavaDoc> _reserved;
56
57   private static final Object JavaDoc LOCK = new Object JavaDoc();
58   
59   // Saved keepalive stream for a new request.
60
private static HttpStream _savedStream;
61   // Time the stream was saved
62
private static long _saveTime;
63   
64   private long _socketTimeout = 30000L;
65
66   private boolean _isSSL;
67
68   private Socket JavaDoc _s;
69   private InputStream JavaDoc _is;
70   private OutputStream JavaDoc _os;
71   private ReadStream _rs;
72   private WriteStream _ws;
73
74   // The server's host name
75
private String JavaDoc _host;
76   // The server's port
77
private int _port;
78
79   private String JavaDoc _virtualHost;
80
81   // the method
82
private String JavaDoc _method;
83   // true for a HEAD stream
84
private boolean _isHead;
85   // true for a POST stream
86
private boolean _isPost;
87
88   // buffer containing the POST data
89
private MemoryStream _tempStream;
90
91   // true if keepalive is allowed
92
private boolean _isKeepalive = true;
93   // true after the request has been sent
94
private boolean _didGet;
95   // content length from the returned response
96
private int _contentLength;
97   // true if the response was chunked
98
private boolean _isChunked;
99   // length of the current chunk, -1 on eof
100
private int _chunkLength;
101   // the request is done
102
private boolean _isRequestDone;
103
104   private HashMap JavaDoc<String JavaDoc,Object JavaDoc> _attributes;
105
106   // Used to read unread bytes on recycle.
107
private byte []_tempBuffer;
108
109   /**
110    * Create a new HTTP stream.
111    */

112   private HttpStream(Path path, String JavaDoc host, int port, Socket JavaDoc s)
113     throws IOException JavaDoc
114   {
115     _s = s;
116
117     _host = host;
118     _port = port;
119     
120     _is = _s.getInputStream();
121     _os = _s.getOutputStream();
122
123     _ws = VfsStream.openWrite(_os);
124     _rs = VfsStream.openRead(_is, _ws);
125
126     _attributes = new HashMap JavaDoc<String JavaDoc,Object JavaDoc>();
127
128     init(path);
129   }
130
131   /**
132    * Opens a new HTTP stream for reading, i.e. a GET request.
133    *
134    * @param path the URL for the stream
135    *
136    * @return the opened stream
137    */

138   static HttpStreamWrapper openRead(HttpPath path) throws IOException JavaDoc
139   {
140     HttpStream stream = createStream(path);
141     stream._isPost = false;
142
143     return new HttpStreamWrapper(stream);
144   }
145
146   /**
147    * Opens a new HTTP stream for reading and writing, i.e. a POST request.
148    *
149    * @param path the URL for the stream
150    *
151    * @return the opened stream
152    */

153   static HttpStreamWrapper openReadWrite(HttpPath path) throws IOException JavaDoc
154   {
155     HttpStream stream = createStream(path);
156     stream._isPost = true;
157
158     return new HttpStreamWrapper(stream);
159   }
160
161   /**
162    * Creates a new HTTP stream. If there is a saved connection to
163    * the same host, use it.
164    *
165    * @param path the URL for the stream
166    *
167    * @return the opened stream
168    */

169   static private HttpStream createStream(HttpPath path) throws IOException JavaDoc
170   {
171     String JavaDoc host = path.getHost();
172     int port = path.getPort();
173
174     HttpStream stream = null;
175     long streamTime = 0;
176     synchronized (LOCK) {
177       if (_savedStream != null &&
178           host.equals(_savedStream.getHost()) &&
179           port == _savedStream.getPort()) {
180         stream = _savedStream;
181         streamTime = _saveTime;
182         _savedStream = null;
183       }
184     }
185
186     if (stream == null) {
187     }
188     // if the stream is still valid, use it
189
else if (Alarm.getCurrentTime() < streamTime + 5000) {
190       stream.init(path);
191       return stream;
192     }
193     // if the stream has timed out, close it
194
else {
195       try {
196         stream._isKeepalive = false;
197         stream.close();
198       } catch (IOException JavaDoc e) {
199         log.log(Level.FINE, e.toString(), e);
200       }
201     }
202
203     Socket JavaDoc s;
204
205     try {
206       s = new Socket JavaDoc(host, port);
207       
208       if (path instanceof HttpsPath) {
209         SSLContext context = SSLContext.getInstance("TLS");
210
211     javax.net.ssl.TrustManager tm =
212       new javax.net.ssl.X509TrustManager() {
213         public java.security.cert.X509Certificate JavaDoc[]
214           getAcceptedIssuers() {
215           return null;
216         }
217         public void checkClientTrusted(
218                        java.security.cert.X509Certificate JavaDoc[] cert, String JavaDoc foo) {
219         }
220         public void checkServerTrusted(
221                        java.security.cert.X509Certificate JavaDoc[] cert, String JavaDoc foo) {
222         }
223       };
224
225       
226         context.init(null, new javax.net.ssl.TrustManager[] { tm }, null);
227         SSLSocketFactory factory = context.getSocketFactory();
228
229         s = factory.createSocket(s, host, port, true);
230       }
231     } catch (ConnectException JavaDoc e) {
232       throw new ConnectException JavaDoc(path.getURL() + ": " + e.getMessage());
233     } catch (Exception JavaDoc e) {
234       throw new ConnectException JavaDoc(path.getURL() + ": " + e.toString());
235     }
236
237     int socketTimeout = 300 * 1000;
238
239     try {
240       s.setSoTimeout(socketTimeout);
241     } catch (Exception JavaDoc e) {
242     }
243           
244     return new HttpStream(path, host, port, s);
245   }
246
247   /**
248    * Initializes the stream for the next request.
249    */

250   private void init(Path path)
251   {
252     _contentLength = -1;
253     _isChunked = false;
254     _isRequestDone = false;
255     _didGet = false;
256     _isPost = false;
257     _isHead = false;
258     _method = null;
259     _attributes.clear();
260     
261     setPath(path);
262
263     if (path instanceof HttpPath)
264       _virtualHost = ((HttpPath) path).getVirtualHost();
265   }
266
267   /**
268    * Set if this should be an SSL connection.
269    */

270   public void setSSL(boolean isSSL)
271   {
272     _isSSL = isSSL;
273   }
274
275   /**
276    * Set if this should be an SSL connection.
277    */

278   public boolean isSSL()
279   {
280     return _isSSL;
281   }
282
283   /**
284    * Sets the method
285    */

286   public void setMethod(String JavaDoc method)
287   {
288     _method = method;
289   }
290
291   /**
292    * Sets true if we're only interested in the head.
293    */

294   public void setHead(boolean isHead)
295   {
296     _isHead = isHead;
297   }
298
299   /**
300    * Returns the stream's host.
301    */

302   public String JavaDoc getHost()
303   {
304     return _host;
305   }
306
307   /**
308    * Returns the stream's port.
309    */

310   public int getPort()
311   {
312     return _port;
313   }
314   
315   /**
316    * Returns a header from the response returned from the HTTP server.
317    *
318    * @param name name of the header
319    * @return the header value.
320    */

321   public Object JavaDoc getAttribute(String JavaDoc name)
322     throws IOException JavaDoc
323   {
324     if (! _didGet)
325       getConnInput();
326     
327     return _attributes.get(name.toLowerCase());
328   }
329
330   /**
331    * Returns an iterator of the returned header names.
332    */

333   public Iterator JavaDoc getAttributeNames()
334     throws IOException JavaDoc
335   {
336     if (! _didGet)
337       getConnInput();
338
339     return _attributes.keySet().iterator();
340   }
341
342   /**
343    * Sets a header for the request.
344    */

345   public void setAttribute(String JavaDoc name, Object JavaDoc value)
346   {
347     if (name.equals("method"))
348       setMethod((String JavaDoc) value);
349     else if (name.equals("socket-timeout")) {
350       if (value instanceof Integer JavaDoc) {
351         int socketTimeout = ((Integer JavaDoc) value).intValue();
352
353         if (socketTimeout > 0) {
354           try {
355             if (_s != null)
356               _s.setSoTimeout(socketTimeout);
357           } catch (Exception JavaDoc e) {
358
359           }
360         }
361       }
362     }
363     else
364       _attributes.put(name.toLowerCase(), value);
365   }
366
367   /**
368    * Remove a header for the request.
369    */

370   public void removeAttribute(String JavaDoc name)
371   {
372     _attributes.remove(name.toLowerCase());
373   }
374
375   /**
376    * Sets the timeout.
377    */

378   public void setSocketTimeout(long timeout)
379     throws SocketException JavaDoc
380   {
381     if (_s != null)
382       _s.setSoTimeout((int) timeout);
383   }
384
385   /**
386    * The stream is always writable (?)
387    */

388   public boolean canWrite()
389   {
390     return true;
391   }
392
393   /**
394    * Writes a buffer to the underlying stream.
395    *
396    * @param buffer the byte array to write.
397    * @param offset the offset into the byte array.
398    * @param length the number of bytes to write.
399    * @param isEnd true when the write is flushing a close.
400    */

401   public void write(byte []buf, int offset, int length, boolean isEnd)
402     throws IOException JavaDoc
403   {
404     if (! _isPost)
405       return;
406
407     if (_tempStream == null)
408       _tempStream = new MemoryStream();
409
410     _tempStream.write(buf, offset, length, isEnd);
411   }
412
413   /**
414    * The stream is readable.
415    */

416   public boolean canRead()
417   {
418     return true;
419   }
420
421   /**
422    * Read data from the connection. If the request hasn't yet been sent
423    * to the server, send it.
424    */

425   public int read(byte []buf, int offset, int length) throws IOException JavaDoc
426   {
427     try {
428       return readInt(buf, offset, length);
429     } catch (IOException JavaDoc e) {
430       _isKeepalive = false;
431       throw e;
432     } catch (RuntimeException JavaDoc e) {
433       _isKeepalive = false;
434       throw e;
435     }
436   }
437   
438   /**
439    * Read data from the connection. If the request hasn't yet been sent
440    * to the server, send it.
441    */

442   public int readInt(byte []buf, int offset, int length) throws IOException JavaDoc
443   {
444     if (! _didGet)
445       getConnInput();
446
447     if (_isRequestDone)
448       return -1;
449
450     try {
451       int len = length;
452
453       if (_isChunked) {
454     if (_chunkLength == 0) {
455       int ch;
456
457       for (ch = _rs.read();
458            ch >= 0 && (ch == '\r' || ch == '\n' || ch == ' ');
459            ch = _rs.read()) {
460       }
461         
462       for (; ch >= 0 && ch != '\n'; ch = _rs.read()) {
463         if (ch >= '0' && ch <= '9')
464           _chunkLength = 16 * _chunkLength + ch - '0';
465         else if (ch >= 'a' && ch <= 'f')
466           _chunkLength = 16 * _chunkLength + ch - 'a' + 10;
467         else if (ch >= 'A' && ch <= 'F')
468           _chunkLength = 16 * _chunkLength + ch - 'A' + 10;
469       }
470
471       if (_chunkLength == 0) {
472         _isRequestDone = true;
473         return -1;
474       }
475     }
476     else if (_chunkLength < 0)
477       return -1;
478       
479     if (_chunkLength < len)
480       len = _chunkLength;
481       }
482       else if (_contentLength < 0) {
483       }
484       else if (_contentLength == 0) {
485     _isRequestDone = true;
486     return -1;
487       }
488       else if (_contentLength < len)
489     len = _contentLength;
490
491       len = _rs.read(buf, offset, len);
492
493       if (len < 0) {
494       }
495       else if (_isChunked)
496     _chunkLength -= len;
497       else if (_contentLength > 0)
498     _contentLength -= len;
499
500       return len;
501     } catch (IOException JavaDoc e) {
502       _isKeepalive = false;
503       throw e;
504     } catch (RuntimeException JavaDoc e) {
505       _isKeepalive = false;
506       throw e;
507     }
508   }
509
510   /**
511    * Sends the request and initializes the response.
512    */

513   private void getConnInput() throws IOException JavaDoc
514   {
515     if (_didGet)
516       return;
517
518     try {
519       getConnInputImpl();
520     } catch (IOException JavaDoc e) {
521       _isKeepalive = false;
522       throw e;
523     } catch (RuntimeException JavaDoc e) {
524       _isKeepalive = false;
525       throw e;
526     }
527   }
528
529   /**
530    * Send the request to the server, wait for the response and parse
531    * the headers.
532    */

533   private void getConnInputImpl() throws IOException JavaDoc
534   {
535     if (_didGet)
536       return;
537
538     _didGet = true;
539
540     if (_method != null) {
541       _ws.print(_method);
542       _ws.print(' ');
543     }
544     else if (_isPost)
545       _ws.print("POST ");
546     else if (_isHead)
547       _ws.print("HEAD ");
548     else
549       _ws.print("GET ");
550
551     // Not splitting query? Also fullpath?
552
_ws.print(_path.getPath());
553
554     if (_path.getQuery() != null) {
555       _ws.print("?");
556       _ws.print(_path.getQuery());
557     }
558     _ws.print(" HTTP/1.1\r\n");
559     _ws.print("Host: ");
560     if (_virtualHost != null)
561       _ws.print(_virtualHost);
562     else {
563       _ws.print(_path.getHost());
564       if (_path.getPort() != 80) {
565     _ws.print(":");
566     _ws.print(String.valueOf(_path.getPort()));
567       }
568     }
569     _ws.print("\r\n");
570     
571     Object JavaDoc userAgent = getAttribute("User-Agent");
572     if (userAgent == null)
573       _ws.print("User-Agent: Mozilla/4.0 (compatible; Resin 1.0; JDK)\r\n");
574     else
575       _ws.print("User-Agent: " + userAgent + "\r\n");
576     Iterator JavaDoc iter = getAttributeNames();
577     while (iter.hasNext()) {
578       String JavaDoc name = (String JavaDoc) iter.next();
579       if (_reserved.get(name.toLowerCase()) == null)
580     _ws.print(name + ": " + getAttribute(name) + "\r\n");
581     }
582     if (! _isKeepalive)
583       _ws.print("Connection: close\r\n");
584     
585     if (_isPost) {
586       int length = 0;
587       if (_tempStream != null)
588     length = _tempStream.getLength();
589       _ws.print("Content-Length: ");
590       _ws.print(length);
591       _ws.print("\r\n");
592     }
593     _ws.print("\r\n");
594
595     if (_isPost) {
596       MemoryStream tempStream = _tempStream;
597       _tempStream = null;
598       if (tempStream != null) {
599     tempStream.writeToStream(_ws);
600     tempStream.destroy();
601       }
602     }
603
604     _attributes.clear();
605
606     parseHeaders();
607
608     if (_isHead)
609       _isRequestDone = true;
610   }
611
612   /**
613    * Parse the headers returned from the server.
614    */

615   private void parseHeaders() throws IOException JavaDoc
616   {
617     CharBuffer line = new CharBuffer();
618
619     // Skip blank lines
620
int count = 0;
621     do {
622       line.clear();
623       if (! _rs.readln(line)) {
624         _isKeepalive = false;
625         return;
626       }
627     } while (line.length() == 0 && ++count < 10);
628
629     if (line.length() == 0) {
630       _isKeepalive = false;
631       return;
632     }
633
634     if (line.startsWith("HTTP/1.1 100")) {
635       count = 100;
636       do {
637     line.clear();
638     if (! _rs.readln(line)) {
639       _isKeepalive = false;
640       return;
641     }
642       } while (line.length() != 0 && count-- > 0);
643       
644       count = 100;
645       do {
646     line.clear();
647     if (! _rs.readln(line)) {
648       _isKeepalive = false;
649       return;
650     }
651       } while (line.length() == 0 && count-- > 0);
652     }
653
654     if (line.length() == 0) {
655       _isKeepalive = false;
656       return;
657     }
658
659     int i = 0;
660     for (i = 0; i < line.length() && line.charAt(i) != ' '; i++) {
661     }
662     
663     for (; i < line.length() && line.charAt(i) == ' '; i++) {
664     }
665     
666     int status = 0;
667     for (; i < line.length(); i++) {
668       char ch = line.charAt(i);
669       if (ch >= '0' && ch <= '9')
670         status = 10 * status + ch - '0';
671       else
672         break;
673     }
674
675     if (status != 200)
676       _isKeepalive = false;
677     else if (! line.startsWith("HTTP/1.1 "))
678       _isKeepalive = false;
679
680     _attributes.put("status", String.valueOf(status));
681
682     CharBuffer key = new CharBuffer();
683     while (true) {
684       line.clear();
685       if (! _rs.readln(line) || line.length() == 0)
686     break;
687
688       int lineLength = line.length();
689       
690       for (i = 0;
691            i < lineLength && Character.isWhitespace(line.charAt(i));
692            i++) {
693       }
694
695       key.clear();
696       for (;
697            i < lineLength && ! Character.isWhitespace(line.charAt(i)) &&
698              line.charAt(i) != ':';
699            i++) {
700         key.append((char) line.charAt(i));
701       }
702
703       for (;
704            i < lineLength && Character.isWhitespace(line.charAt(i));
705            i++) {
706       }
707
708       if (key.length() == 0 || lineLength <= i || line.charAt(i) != ':')
709         continue;
710
711       for (i++;
712            i < lineLength && Character.isWhitespace(line.charAt(i));
713            i++) {
714       }
715
716       key.toLowerCase();
717       String JavaDoc value = line.substring(i);
718
719       if (log.isLoggable(Level.FINE))
720         log.fine(key + ": " + value);
721       
722       if (key.matches("content-length")) {
723         _contentLength = Integer.parseInt(value);
724       }
725       else if (key.matches("connection") &&
726                value.equalsIgnoreCase("close")) {
727         _isKeepalive = false;
728       }
729       else if (key.matches("transfer-encoding") &&
730                value.equalsIgnoreCase("chunked")) {
731         
732         _isChunked = true;
733         _chunkLength = 0;
734       }
735
736       _attributes.put(key.toLowerCase().toString(), value);
737     }
738   }
739
740   /**
741    * Returns the bytes still available.
742    */

743   public int getAvailable() throws IOException JavaDoc
744   {
745     if (! _didGet)
746       getConnInput();
747
748     if (_contentLength > 0)
749       return _contentLength;
750     else
751       return _rs.getAvailable();
752   }
753
754   /**
755    * Close the connection.
756    */

757   public void close() throws IOException JavaDoc
758   {
759     if (_isKeepalive) {
760       // If recycling, read any unread data
761
if (! _didGet)
762         getConnInput();
763
764       if (! _isRequestDone) {
765         if (_tempBuffer == null)
766           _tempBuffer = new byte[256];
767
768         try {
769           while (read(_tempBuffer, 0, _tempBuffer.length) > 0) {
770           }
771         } catch (IOException JavaDoc e) {
772           _isKeepalive = false;
773         }
774       }
775     }
776
777     if (com.caucho.util.Alarm.isTest())
778       _isKeepalive = false; // XXX:
779

780     if (_isKeepalive) {
781       HttpStream oldSaved;
782       long now = Alarm.getCurrentTime();
783       synchronized (LOCK) {
784         oldSaved = _savedStream;
785         _savedStream = this;
786         _saveTime = now;
787       }
788
789       if (oldSaved != null && oldSaved != this) {
790         oldSaved._isKeepalive = false;
791         oldSaved.close();
792       }
793
794       return;
795     }
796
797     try {
798       try {
799         if (_ws != null)
800           _ws.close();
801       } catch (Throwable JavaDoc e) {
802       }
803       _ws = null;
804
805       try {
806         if (_rs != null)
807           _rs.close();
808       } catch (Throwable JavaDoc e) {
809       }
810       _rs = null;
811
812       try {
813         if (_os != null)
814           _os.close();
815       } catch (Throwable JavaDoc e) {
816       }
817       _os = null;
818
819       try {
820         if (_is != null)
821           _is.close();
822       } catch (Throwable JavaDoc e) {
823       }
824       _is = null;
825     } finally {
826       if (_s != null)
827     _s.close();
828       _s = null;
829     }
830   }
831
832   static {
833     _reserved = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
834     _reserved.put("user-agent", "");
835     _reserved.put("content-length", "");
836     _reserved.put("content-encoding", "");
837     _reserved.put("connection", "");
838     _reserved.put("host", "");
839   }
840 }
841
Popular Tags