KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > server > connection > ResponseStream


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.server.connection;
31
32 import com.caucho.log.Log;
33 import com.caucho.server.webapp.WebApp;
34 import com.caucho.util.L10N;
35 import com.caucho.vfs.ClientDisconnectException;
36 import com.caucho.vfs.WriteStream;
37
38 import javax.servlet.ServletContext JavaDoc;
39 import java.io.IOException JavaDoc;
40 import java.io.OutputStream JavaDoc;
41 import java.util.logging.Level JavaDoc;
42 import java.util.logging.Logger JavaDoc;
43
44 class ResponseStream extends ToByteResponseStream {
45   static final Logger JavaDoc log = Log.open(ResponseStream.class);
46   
47   static final L10N L = new L10N(ResponseStream.class);
48
49   private static final int _tailChunkedLength = 7;
50   private static final byte []_tailChunked =
51     new byte[] {'\r', '\n', '0', '\r', '\n', '\r', '\n'};
52   
53   private final AbstractHttpResponse _response;
54   
55   private WriteStream _next;
56   
57   private OutputStream JavaDoc _cacheStream;
58   private long _cacheMaxLength;
59   // used for the direct copy and caching
60
private int _bufferStartOffset;
61   
62   private boolean _chunkedEncoding;
63   
64   private int _bufferSize;
65   private boolean _disableAutoFlush;
66   
67   // bytes actually written
68
private int _contentLength;
69   // True for the first chunk
70
private boolean _isFirst;
71   private boolean _isDisconnected;
72   private boolean _isCommitted;
73
74   private boolean _allowFlush = true;
75   private boolean _isHead = false;
76   private boolean _isClosed = false;
77   
78   private final byte []_buffer = new byte[16];
79
80   ResponseStream(AbstractHttpResponse response)
81   {
82     _response = response;
83   }
84
85   public void init(WriteStream next)
86   {
87     _next = next;
88   }
89   
90   /**
91    * initializes the Response stream at the beginning of a request.
92    */

93   public void start()
94   {
95     super.start();
96     
97     _chunkedEncoding = false;
98
99     _contentLength = 0;
100     _allowFlush = true;
101     _disableAutoFlush = false;
102     _isClosed = false;
103     _isHead = false;
104     _cacheStream = null;
105     _isDisconnected = false;
106     _isCommitted = false;
107     _isFirst = true;
108     _bufferStartOffset = 0;
109   }
110
111   /**
112    * Returns true for a Caucho response stream.
113    */

114   public boolean isCauchoResponseStream()
115   {
116     return true;
117   }
118
119   /**
120    * Sets the underlying cache stream for a cached request.
121    *
122    * @param cache the cache stream.
123    */

124   public void setByteCacheStream(OutputStream JavaDoc cacheStream)
125   {
126     _cacheStream = cacheStream;
127     
128     CauchoRequest req = _response.getRequest();
129     WebApp app = req.getWebApp();
130     _cacheMaxLength = app.getCacheMaxLength();
131   }
132
133   /**
134    * Response stream is a writable stream.
135    */

136   public boolean canWrite()
137   {
138     return true;
139   }
140
141   void setFlush(boolean flush)
142   {
143     _allowFlush = flush;
144   }
145
146   public void setAutoFlush(boolean isAutoFlush)
147   {
148     setDisableAutoFlush(! isAutoFlush);
149   }
150
151   void setDisableAutoFlush(boolean disable)
152   {
153     _disableAutoFlush = disable;
154   }
155
156   public void setHead()
157   {
158     _isHead = true;
159     _bufferSize = 0;
160   }
161
162   public boolean isHead()
163   {
164     return _isHead;
165   }
166
167   public int getContentLength()
168   {
169     return _contentLength;
170   }
171
172   public void setBufferSize(int size)
173   {
174     if (isCommitted())
175       throw new IllegalStateException JavaDoc(L.l("Buffer size cannot be set after commit"));
176
177     super.setBufferSize(size);
178   }
179
180   public boolean isCommitted()
181   {
182     // jsp/17ec
183
return _isCommitted || _isClosed;
184   }
185
186   public void clear()
187     throws IOException JavaDoc
188   {
189     clearBuffer();
190     
191     if (_isCommitted)
192       throw new IOException JavaDoc(L.l("can't clear response after writing headers"));
193   }
194   
195   public void clearBuffer()
196   {
197     super.clearBuffer();
198
199     if (! _isCommitted) {
200       // jsp/15la
201
_isFirst = true;
202       _bufferStartOffset = 0;
203       _response.setHeaderWritten(false);
204     }
205
206     _next.setBufferOffset(_bufferStartOffset);
207   }
208
209   /**
210    * Clear the closed state, because of the NOT_MODIFIED
211    */

212   public void clearClosed()
213   {
214     _isClosed = false;
215   }
216
217   private void writeHeaders(int length)
218     throws IOException JavaDoc
219   {
220     _chunkedEncoding = _response.writeHeaders(_next, length);
221   }
222
223   /**
224    * Returns the byte buffer.
225    */

226   public byte []getBuffer()
227     throws IOException JavaDoc
228   {
229     flushBuffer();
230
231     return _next.getBuffer();
232   }
233
234   /**
235    * Returns the byte offset.
236    */

237   public int getBufferOffset()
238     throws IOException JavaDoc
239   {
240     byte []buffer;
241     int offset;
242
243     flushBuffer();
244
245     offset = _next.getBufferOffset();
246
247     if (! _chunkedEncoding) {
248       _bufferStartOffset = offset;
249       return offset;
250     }
251     else if (_bufferStartOffset > 0) {
252       return offset;
253     }
254
255     // chunked allocates 8 bytes for the chunk header
256
buffer = _next.getBuffer();
257     if (buffer.length - offset < 8) {
258       _isCommitted = true;
259       _next.flushBuffer();
260       
261       buffer = _next.getBuffer();
262       offset = _next.getBufferOffset();
263     }
264
265     _bufferStartOffset = offset + 8;
266     _next.setBufferOffset(offset + 8);
267
268     return _bufferStartOffset;
269   }
270
271   /**
272    * Sets the next buffer
273    */

274   public byte []nextBuffer(int offset)
275     throws IOException JavaDoc
276   {
277     if (_isClosed)
278       return _next.getBuffer();
279     
280     _isCommitted = true;
281     
282     int startOffset = _bufferStartOffset;
283     _bufferStartOffset = 0;
284
285     int length = offset - startOffset;
286     long lengthHeader = _response.getContentLengthHeader();
287
288     if (lengthHeader > 0 && lengthHeader < _contentLength + length) {
289       lengthException(_next.getBuffer(), startOffset, length, lengthHeader);
290
291       length = (int) (lengthHeader - _contentLength);
292       offset = startOffset + length;
293     }
294
295     _contentLength += length;
296
297     try {
298       if (_isHead) {
299     return _next.getBuffer();
300       }
301       else if (_chunkedEncoding) {
302     if (length == 0)
303       throw new IllegalStateException JavaDoc();
304       
305     byte []buffer = _next.getBuffer();
306
307     writeChunk(buffer, startOffset, length);
308
309     buffer = _next.nextBuffer(offset);
310           
311     if (log.isLoggable(Level.FINE))
312       log.fine("[" + dbgId() + "] write-chunk(" + offset + ")");
313
314     _bufferStartOffset = 8 + _next.getBufferOffset();
315     _next.setBufferOffset(_bufferStartOffset);
316
317     return buffer;
318       }
319       else {
320     if (_cacheStream != null)
321       writeCache(_next.getBuffer(), startOffset, length);
322     
323     byte []buffer = _next.nextBuffer(offset);
324           
325     if (log.isLoggable(Level.FINE))
326       log.fine("[" + dbgId() + "] write-chunk(" + offset + ")");
327
328     return buffer;
329       }
330     } catch (ClientDisconnectException e) {
331       _response.killCache();
332
333       if (_response.isIgnoreClientDisconnect()) {
334         _isDisconnected = true;
335     return _next.getBuffer();
336       }
337       else
338         throw e;
339     } catch (IOException JavaDoc e) {
340       _response.killCache();
341       
342       throw e;
343     }
344   }
345
346   /**
347    * Sets the byte offset.
348    */

349   public void setBufferOffset(int offset)
350     throws IOException JavaDoc
351   {
352     if (_isClosed)
353       return;
354     
355     int startOffset = _bufferStartOffset;
356     if (offset == startOffset)
357       return;
358     
359     int length = offset - startOffset;
360     long lengthHeader = _response.getContentLengthHeader();
361
362     if (lengthHeader > 0 && lengthHeader < _contentLength + length) {
363       lengthException(_next.getBuffer(), startOffset, length, lengthHeader);
364
365       length = (int) (lengthHeader - _contentLength);
366       offset = startOffset + length;
367     }
368
369     _contentLength += length;
370     
371     if (_cacheStream != null && ! _chunkedEncoding) {
372       _bufferStartOffset = offset;
373       writeCache(_next.getBuffer(), startOffset, length);
374     }
375
376     if (! _isHead) {
377       _next.setBufferOffset(offset);
378     }
379   }
380
381   /**
382    * Writes the next chunk of data to the response stream.
383    *
384    * @param buf the buffer containing the data
385    * @param offset start offset into the buffer
386    * @param length length of the data in the buffer
387    */

388   protected void writeNext(byte []buf, int offset, int length,
389                boolean isFinished)
390     throws IOException JavaDoc
391   {
392     try {
393       if (_isClosed)
394     return;
395
396       if (_disableAutoFlush && ! isFinished)
397     throw new IOException JavaDoc(L.l("auto-flushing has been disabled"));
398       
399       boolean isFirst = _isFirst;
400       _isFirst = false;
401
402       if (! isFirst) {
403       }
404       else if (isFinished)
405     writeHeaders(getBufferLength());
406       else
407     writeHeaders(-1);
408
409       int bufferStart = _bufferStartOffset;
410       int bufferOffset = _next.getBufferOffset();
411
412       // server/05e2
413
if (length == 0 && ! isFinished && bufferStart == bufferOffset)
414         return;
415
416       long contentLengthHeader = _response.getContentLengthHeader();
417       // Can't write beyond the content length
418
if (0 < contentLengthHeader &&
419           contentLengthHeader < length + _contentLength) {
420     if (lengthException(buf, offset, length, contentLengthHeader))
421       return;
422
423     length = (int) (contentLengthHeader - _contentLength);
424       }
425
426       if (_next != null && ! _isHead) {
427     if (length > 0 && log.isLoggable(Level.FINE)) {
428       String JavaDoc id;
429       if (_response.getRequest() instanceof AbstractHttpRequest) {
430         Connection conn = ((AbstractHttpRequest) _response.getRequest()).getConnection();
431         if (conn != null)
432           id = String.valueOf(conn.getId());
433         else
434           id = "jni";
435       }
436       else
437         id = "inc";
438         
439       log.fine("[" + id + "] chunk: " + length);
440     }
441     
442     if (! _chunkedEncoding) {
443       byte []nextBuffer = _next.getBuffer();
444       int nextOffset = _next.getBufferOffset();
445
446       if (nextOffset + length < nextBuffer.length) {
447         System.arraycopy(buf, offset, nextBuffer, nextOffset, length);
448         _next.setBufferOffset(nextOffset + length);
449       }
450       else {
451         _isCommitted = true;
452         _next.write(buf, offset, length);
453
454         if (log.isLoggable(Level.FINE))
455           log.fine("[" + dbgId() + "] write-data(" + _tailChunkedLength + ")");
456       }
457
458       if (_cacheStream != null)
459         writeCache(buf, offset, length);
460     }
461     else {
462       byte []buffer = _next.getBuffer();
463       int writeLength = length;
464
465       if (bufferStart == 0 && writeLength > 0) {
466         bufferStart = bufferOffset + 8;
467         bufferOffset = bufferStart;
468       }
469
470       while (writeLength > 0) {
471         int sublen = buffer.length - bufferOffset;
472
473         if (writeLength < sublen)
474           sublen = writeLength;
475
476         System.arraycopy(buf, offset, buffer, bufferOffset, sublen);
477
478         writeLength -= sublen;
479         offset += sublen;
480         bufferOffset += sublen;
481
482         if (writeLength > 0) {
483           int delta = bufferOffset - bufferStart;
484           writeChunk(buffer, bufferStart, delta);
485                
486           _isCommitted = true;
487           buffer = _next.nextBuffer(bufferOffset);
488           
489           if (log.isLoggable(Level.FINE))
490         log.fine("[" + dbgId() + "] write-chunk(" + bufferOffset + ")");
491           
492           bufferStart = _next.getBufferOffset() + 8;
493           bufferOffset = bufferStart;
494         }
495       }
496
497       _next.setBufferOffset(bufferOffset);
498       _bufferStartOffset = bufferStart;
499     }
500       }
501
502       if (! _isDisconnected)
503         _contentLength += length;
504     } catch (ClientDisconnectException e) {
505       // server/183c
506
_response.killCache();
507
508       if (_response.isIgnoreClientDisconnect())
509         _isDisconnected = true;
510       else {
511         throw e;
512       }
513     }
514   }
515   
516   private boolean lengthException(byte []buf, int offset, int length,
517                   long contentLengthHeader)
518   {
519     if (_isDisconnected || _isHead || _isClosed) {
520     }
521     else if (contentLengthHeader < _contentLength) {
522       CauchoRequest request = _response.getRequest();
523       ServletContext JavaDoc app = request.getWebApp();
524       
525       Exception JavaDoc exn =
526       new IllegalStateException JavaDoc(L.l("{0}: tried to write {1} bytes with content-length {2}.",
527                     request.getRequestURL(),
528                     "" + (length + _contentLength),
529                     "" + contentLengthHeader));
530
531       if (app != null)
532     app.log(exn.getMessage(), exn);
533       else
534     exn.printStackTrace();
535
536       return false;
537     }
538     
539     for (int i = (int) (offset + contentLengthHeader - _contentLength);
540      i < offset + length;
541      i++) {
542       int ch = buf[i];
543
544       if (ch != '\r' && ch != '\n' && ch != ' ' && ch != '\t') {
545     CauchoRequest request = _response.getRequest();
546     ServletContext JavaDoc app = request.getWebApp();
547     String JavaDoc graph = "";
548         
549     if (Character.isLetterOrDigit((char) ch))
550       graph = "'" + (char) ch + "', ";
551         
552     Exception JavaDoc exn =
553       new IllegalStateException JavaDoc(L.l("{0}: tried to write {1} bytes with content-length {2} (At {3}char={4}).",
554                     request.getRequestURL(),
555                     "" + (length + _contentLength),
556                     "" + contentLengthHeader,
557                     graph,
558                     "" + ch));
559
560     if (app != null)
561       app.log(exn.getMessage(), exn);
562     else
563       exn.printStackTrace();
564     break;
565       }
566     }
567         
568     length = (int) (contentLengthHeader - _contentLength);
569     return (length <= 0);
570   }
571
572   public void flushBuffer()
573     throws IOException JavaDoc
574   {
575     super.flushBuffer();
576     
577     _isCommitted = true;
578   }
579
580   /**
581    * Flushes the buffered response to the output stream.
582    */

583   public void flush()
584     throws IOException JavaDoc
585   {
586     try {
587       _disableAutoFlush = false;
588       _isCommitted = true;
589
590       if (_allowFlush && ! _isClosed) {
591         flushBuffer();
592
593     if (_chunkedEncoding) {
594       int bufferStart = _bufferStartOffset;
595       _bufferStartOffset = 0;
596
597       if (bufferStart > 0) {
598         int bufferOffset = _next.getBufferOffset();
599
600         if (bufferStart != bufferOffset) {
601           writeChunk(_next.getBuffer(), bufferStart,
602              bufferOffset - bufferStart);
603         }
604         else
605           _next.setBufferOffset(bufferStart - 8);
606       }
607     }
608     else {
609       // jsp/01cf
610
_bufferStartOffset = 0;
611     }
612     
613         if (_next != null)
614           _next.flush();
615       }
616     } catch (ClientDisconnectException e) {
617       if (_response.isIgnoreClientDisconnect())
618         _isDisconnected = true;
619       else
620         throw e;
621     }
622   }
623
624   /**
625    * Flushes the buffered response to the output stream.
626    */

627   public void flushByte()
628     throws IOException JavaDoc
629   {
630     flush();
631   }
632
633   /**
634    * Flushes the buffered response to the writer.
635    */

636   public void flushChar()
637     throws IOException JavaDoc
638   {
639     flush();
640   }
641
642   /**
643    * Flushes the buffered response to the output stream.
644    */

645   /*
646   public void flushBuffer()
647     throws IOException
648   {
649     super.flushBuffer();
650
651     // jsp/15la
652     // _isCommitted = true;
653   }
654   */

655   
656   /**
657    * Complete the request.
658    */

659   public void finish()
660     throws IOException JavaDoc
661   {
662     boolean isClosed = _isClosed;
663
664     if (_next == null || isClosed) {
665       _isClosed = true;
666       return;
667     }
668
669     _disableAutoFlush = false;
670
671     flushCharBuffer();
672
673     _isFinished = true;
674     _allowFlush = true;
675     
676     flushBuffer();
677
678     int bufferStart = _bufferStartOffset;
679     _bufferStartOffset = 0;
680     _isClosed = true;
681     
682     // flushBuffer can force 304 and then a cache write which would
683
// complete the finish.
684
if (isClosed || _next == null) {
685       return;
686     }
687     
688     try {
689       if (_chunkedEncoding) {
690     int bufferOffset = _next.getBufferOffset();
691
692     if (bufferStart > 0 && bufferOffset != bufferStart) {
693       byte []buffer = _next.getBuffer();
694
695       writeChunk(buffer, bufferStart, bufferOffset - bufferStart);
696     }
697     
698     _isCommitted = true;
699     _next.write(_tailChunked, 0, _tailChunkedLength);
700
701     if (log.isLoggable(Level.FINE))
702           log.fine("[" + dbgId() + "] write-chunk(" + _tailChunkedLength + ")");
703       }
704
705       CauchoRequest req = _response.getRequest();
706       if (! req.allowKeepalive()) {
707         if (log.isLoggable(Level.FINE)) {
708           String JavaDoc id;
709           if (req instanceof AbstractHttpRequest) {
710             Connection conn = ((AbstractHttpRequest) req).getConnection();
711             if (conn != null)
712               id = String.valueOf(conn.getId());
713             else
714               id = "jni";
715           }
716           else
717             id = "inc";
718           log.fine("[" + id + "] close stream");
719         }
720       
721         _next.close();
722       }
723       /*
724       else if (flush) {
725         //_next.flush();
726         _next.flushBuffer();
727       }
728       */

729     } catch (ClientDisconnectException e) {
730       if (_response.isIgnoreClientDisconnect())
731         _isDisconnected = true;
732       else
733         throw e;
734     }
735   }
736
737   /**
738    * Fills the chunk header.
739    */

740   private void writeChunk(byte []buffer, int start, int length)
741     throws IOException JavaDoc
742   {
743     buffer[start - 8] = (byte) '\r';
744     buffer[start - 7] = (byte) '\n';
745     buffer[start - 6] = hexDigit(length >> 12);
746     buffer[start - 5] = hexDigit(length >> 8);
747     buffer[start - 4] = hexDigit(length >> 4);
748     buffer[start - 3] = hexDigit(length);
749     buffer[start - 2] = (byte) '\r';
750     buffer[start - 1] = (byte) '\n';
751
752     if (_cacheStream != null)
753       writeCache(buffer, start, length);
754   }
755
756   /**
757    * Returns the hex digit for the value.
758    */

759   private static byte hexDigit(int value)
760   {
761     value &= 0xf;
762
763     if (value <= 9)
764       return (byte) ('0' + value);
765     else
766       return (byte) ('a' + value - 10);
767   }
768
769   private void writeCache(byte []buf, int offset, int length)
770     throws IOException JavaDoc
771   {
772     if (length == 0)
773       return;
774     
775     if (_cacheMaxLength < _contentLength) {
776       _cacheStream = null;
777       _response.killCache();
778     }
779     else {
780       _cacheStream.write(buf, offset, length);
781     }
782   }
783
784   private String JavaDoc dbgId()
785   {
786     Object JavaDoc req = _response.getRequest();
787     
788     if (req instanceof AbstractHttpRequest) {
789       Connection conn = ((AbstractHttpRequest) req).getConnection();
790       if (conn != null)
791     return String.valueOf(conn.getId());
792       else
793     return "jni";
794     }
795     else
796       return "inc";
797   }
798
799   /**
800    * Closes the stream.
801    */

802   public void close()
803     throws IOException JavaDoc
804   {
805     finish();
806   }
807 }
808
Popular Tags