View Javadoc

1   /*
2    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3    *
4    * Copyright (c) 2007-2012 Oracle and/or its affiliates. All rights reserved.
5    *
6    * The contents of this file are subject to the terms of either the GNU
7    * General Public License Version 2 only ("GPL") or the Common Development
8    * and Distribution License("CDDL") (collectively, the "License").  You
9    * may not use this file except in compliance with the License.  You can
10   * obtain a copy of the License at
11   * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
12   * or packager/legal/LICENSE.txt.  See the License for the specific
13   * language governing permissions and limitations under the License.
14   *
15   * When distributing the software, include this License Header Notice in each
16   * file and include the License file at packager/legal/LICENSE.txt.
17   *
18   * GPL Classpath Exception:
19   * Oracle designates this particular file as subject to the "Classpath"
20   * exception as provided by Oracle in the GPL Version 2 section of the License
21   * file that accompanied this code.
22   *
23   * Modifications:
24   * If applicable, add the following below the License Header, with the fields
25   * enclosed by brackets [] replaced by your own identifying information:
26   * "Portions Copyright [year] [name of copyright owner]"
27   *
28   * Contributor(s):
29   * If you wish your version of this file to be governed by only the CDDL or
30   * only the GPL Version 2, indicate your decision by adding "[Contributor]
31   * elects to include this software in this distribution under the [CDDL or GPL
32   * Version 2] license."  If you don't indicate a single choice of license, a
33   * recipient has the option to distribute your version of this file under
34   * either the CDDL, the GPL Version 2 or to extend the choice of license to
35   * its licensees as provided above.  However, if you add GPL Version 2 code
36   * and therefore, elected the GPL Version 2 license, then the option applies
37   * only if the new code is made subject to such option by the copyright
38   * holder.
39   *
40   *
41   * This file incorporates work covered by the following copyright and
42   * permission notice:
43   *
44   * Copyright 2004 The Apache Software Foundation
45   *
46   * Licensed under the Apache License, Version 2.0 (the "License");
47   * you may not use this file except in compliance with the License.
48   * You may obtain a copy of the License at
49   *
50   *     http://www.apache.org/licenses/LICENSE-2.0
51   *
52   * Unless required by applicable law or agreed to in writing, software
53   * distributed under the License is distributed on an "AS IS" BASIS,
54   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
55   * See the License for the specific language governing permissions and
56   * limitations under the License.
57   */
58  
59  package com.sun.grizzly.tcp.http11;
60  
61  import com.sun.grizzly.tcp.ActionCode;
62  import com.sun.grizzly.tcp.OutputBuffer;
63  import com.sun.grizzly.tcp.Response;
64  import com.sun.grizzly.util.buf.ByteChunk;
65  import com.sun.grizzly.util.buf.CharChunk;
66  import com.sun.grizzly.util.buf.MessageBytes;
67  import com.sun.grizzly.util.http.HttpMessages;
68  import com.sun.grizzly.util.http.MimeHeaders;
69  
70  import java.io.IOException;
71  import java.io.OutputStream;
72  import java.security.AccessController;
73  import java.security.PrivilegedAction;
74  
75  /**
76   * Output buffer.
77   *
78   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
79   */
80  public class InternalOutputBuffer
81          implements OutputBuffer, ByteChunk.ByteOutputChannel {
82  
83  
84      // -------------------------------------------------------------- Constants
85  
86  
87      // ----------------------------------------------------------- Constructors
88  
89  
90      /**
91       * Default constructor.
92       */
93      public InternalOutputBuffer(Response response) {
94          this(response, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
95      }
96  
97  
98      // START GlassFish Issue 798
99  
100     /**
101      * Create a new InternalOutputBuffer and configure the enable/disable the
102      * socketBuffer mechanism.
103      */
104     public InternalOutputBuffer(Response response, int headerBufferSize,
105             boolean useSocketBuffer) {
106 
107         this(response, headerBufferSize);
108         this.useSocketBuffer = useSocketBuffer;
109         if (useSocketBuffer) {
110             socketBuffer.allocate(headerBufferSize, headerBufferSize);
111         }
112     }
113     // END GlassFish Issue 798
114 
115 
116     /**
117      * Alternate constructor.
118      */
119     public InternalOutputBuffer(Response response, int headerBufferSize) {
120 
121         this.response = response;
122         headers = response.getMimeHeaders();
123 
124         buf = new byte[headerBufferSize];
125 
126         outputStreamOutputBuffer = new OutputStreamOutputBuffer();
127 
128         filterLibrary = new OutputFilter[0];
129         activeFilters = new OutputFilter[0];
130         lastActiveFilter = -1;
131 
132         socketBuffer = new ByteChunk();
133         socketBuffer.setByteOutputChannel(this);
134 
135         committed = false;
136         finished = false;
137 
138     }
139 
140     protected InternalOutputBuffer() {}
141 
142 
143     // ----------------------------------------------------- Instance Variables
144 
145     /**
146      * The last {@link OutputFilter} to be added to the activeFilters list.
147      */
148     private OutputFilter lastOutputFilter = null;
149 
150 
151     /**
152      * Associated Coyote response.
153      */
154     protected Response response;
155 
156 
157     /**
158      * Headers of the associated request.
159      */
160     protected MimeHeaders headers;
161 
162 
163     /**
164      * Committed flag.
165      */
166     protected boolean committed;
167 
168 
169     /**
170      * Finished flag.
171      */
172     protected boolean finished;
173 
174 
175     /**
176      * Pointer to the current read buffer.
177      */
178     protected byte[] buf;
179 
180 
181     /**
182      * Position in the buffer.
183      */
184     protected int pos;
185 
186 
187     /**
188      * Underlying output stream.
189      */
190     protected OutputStream outputStream;
191 
192 
193     /**
194      * Underlying output buffer.
195      */
196     protected OutputBuffer outputStreamOutputBuffer;
197 
198 
199     /**
200      * Filter library.
201      * Note: Filter[0] is always the "chunked" filter.
202      */
203     protected OutputFilter[] filterLibrary;
204 
205 
206     /**
207      * Active filter (which is actually the top of the pipeline).
208      */
209     protected OutputFilter[] activeFilters;
210 
211 
212     /**
213      * Index of the last active filter.
214      */
215     protected int lastActiveFilter;
216 
217 
218     /**
219      * Socket buffer.
220      */
221     protected ByteChunk socketBuffer;
222 
223 
224     /**
225      * Socket buffer (extra buffering to reduce number of packets sent).
226      */
227     protected boolean useSocketBuffer = false;
228 
229 
230     // ------------------------------------------------------------- Properties
231 
232 
233     /**
234      * Set the underlying socket output stream.
235      */
236     public void setOutputStream(OutputStream outputStream) {
237 
238         // FIXME: Check for null ?
239 
240         this.outputStream = outputStream;
241 
242     }
243 
244 
245     /**
246      * Get the underlying socket output stream.
247      */
248     public OutputStream getOutputStream() {
249 
250         return outputStream;
251 
252     }
253 
254 
255     /**
256      * Set the socket buffer size.
257      */
258     public void setSocketBuffer(int socketBufferSize) {
259 
260         if (socketBufferSize > 500) {
261             useSocketBuffer = true;
262             socketBuffer.allocate(socketBufferSize, socketBufferSize);
263         } else {
264             useSocketBuffer = false;
265         }
266 
267     }
268 
269 
270     /**
271      * Add an output filter to the filter library.
272      */
273     public void addFilter(OutputFilter filter) {
274 
275         OutputFilter[] newFilterLibrary =
276                 new OutputFilter[filterLibrary.length + 1];
277         System.arraycopy(filterLibrary, 0, newFilterLibrary, 0, filterLibrary.length);
278         newFilterLibrary[filterLibrary.length] = filter;
279         filterLibrary = newFilterLibrary;
280 
281         activeFilters = new OutputFilter[filterLibrary.length];
282 
283     }
284 
285 
286     /**
287      * Get filters.
288      */
289     public OutputFilter[] getFilters() {
290 
291         return filterLibrary;
292 
293     }
294 
295 
296     /**
297      * Clear filters.
298      */
299     public void clearFilters() {
300 
301         filterLibrary = new OutputFilter[0];
302         lastActiveFilter = -1;
303 
304     }
305 
306 
307     /**
308      * Add the last {@link OutputFilter} that will be invoked when processing
309      * the writing of the response bytes.
310      */
311     public void addLastOutputFilter(OutputFilter lastOutputFilter) {
312         this.lastOutputFilter = lastOutputFilter;
313     }
314 
315 
316     /**
317      * Add an output filter to the filter library.
318      */
319     public void addActiveFilter(OutputFilter filter) {
320 
321         if (lastActiveFilter == -1) {
322             filter.setBuffer(outputStreamOutputBuffer);
323         } else {
324             for (int i = 0; i <= lastActiveFilter; i++) {
325                 if (activeFilters[i] == filter) {
326                     return;
327                 }
328             }
329             filter.setBuffer(activeFilters[lastActiveFilter]);
330         }
331 
332         activeFilters[++lastActiveFilter] = filter;
333 
334         filter.setResponse(response);
335     }
336 
337 
338     // --------------------------------------------------------- Public Methods
339 
340 
341     /**
342      * Flush the response.
343      *
344      * @throws IOException an undelying I/O error occured
345      */
346     public void flush()
347             throws IOException {
348 
349         if (!committed) {
350 
351             // Send the connector a request for commit. The connector should
352             // then validate the headers, send them (using sendHeader) and 
353             // set the filters accordingly.
354             response.action(ActionCode.ACTION_COMMIT, null);
355 
356         }
357 
358         // Flush the current buffer
359         if (useSocketBuffer) {
360             socketBuffer.flushBuffer();
361         }
362 
363     }
364 
365 
366     // START GlassFish Issue 646
367 
368     /**
369      * Flush the buffer.
370      */
371     protected void flush(boolean isFull) throws IOException {
372         // Sending the response header buffer
373         realWriteBytes(buf, 0, pos);
374 
375         if (isFull) {
376             pos = 0;
377         }
378     }
379     // END GlassFish Issue 646
380 
381 
382     /**
383      * Reset current response.
384      *
385      * @throws IllegalStateException if the response has already been committed
386      */
387     public void reset() {
388 
389         if (committed) {
390             throw new IllegalStateException(/*FIXME:Put an error message*/);
391         }
392 
393         // Recycle Request object
394         response.recycle();
395 
396     }
397 
398 
399     /**
400      * Recycle the output buffer. This should be called when closing the
401      * connection.
402      */
403     public void recycle() {
404 
405         // Recycle Request object
406         response.recycle();
407         socketBuffer.recycle();
408 
409         // Recycle filters
410         for (int i = 0; i <= lastActiveFilter; i++) {
411             activeFilters[i].recycle();
412         }
413 
414         outputStream = null;
415         pos = 0;
416         lastActiveFilter = -1;
417         committed = false;
418         finished = false;
419 
420     }
421 
422 
423     /**
424      * End processing of current HTTP request.
425      * Note: All bytes of the current request should have been already
426      * consumed. This method only resets all the pointers so that we are ready
427      * to parse the next HTTP request.
428      */
429     public void nextRequest() {
430 
431         // Recycle Request object
432         response.recycle();
433         if (socketBuffer != null) {
434             socketBuffer.recycle();
435         }
436 
437         // Recycle filters
438         for (int i = 0; i <= lastActiveFilter; i++) {
439             activeFilters[i].recycle();
440         }
441 
442         // Reset pointers
443         pos = 0;
444         lastActiveFilter = -1;
445         committed = false;
446         finished = false;
447 
448     }
449 
450 
451     /**
452      * End request.
453      *
454      * @throws IOException an underlying I/O error occurred
455      */
456     public void endRequest()
457             throws IOException {
458 
459 
460         if (!committed) {
461 
462             // Send the connector a request for commit. The connector should
463             // then validate the headers, send them (using sendHeader) and 
464             // set the filters accordingly.
465             response.action(ActionCode.ACTION_COMMIT, null);
466 
467         }
468 
469         if (finished) {
470             return;
471         }
472 
473         if (lastActiveFilter != -1) {
474             activeFilters[lastActiveFilter].end();
475         }
476 
477         if (useSocketBuffer) {
478             socketBuffer.flushBuffer();
479         }
480 
481         finished = true;
482 
483     }
484 
485 
486     // ------------------------------------------------ HTTP/1.1 Output Methods
487 
488 
489     /**
490      * Send an acknoledgement.
491      */
492     public void sendAck()
493             throws IOException {
494 
495         if (!committed) {
496             realWriteBytes(Constants.ACK_BYTES, 0,
497                     Constants.ACK_BYTES.length);
498         }
499     }
500 
501 
502     /**
503      * Send the response status line.
504      */
505     public void sendStatus() {
506 
507         // Write protocol name
508         write("HTTP/1.1 ");
509 
510         // Write status code
511         int status = response.getStatus();
512         switch (status) {
513             case 200:
514                 write("200");
515                 break;
516             case 400:
517                 write("400");
518                 break;
519             case 404:
520                 write("404");
521                 break;
522             default:
523                 write(status);
524         }
525 
526         write(" ");
527 
528         // Write message
529         String message = null;
530         // Servlet 3.0, section 5:3, the message from sendError is for content body
531         if (response.isAllowCustomReasonPhrase()) {
532             message = response.getMessage();
533         }
534         if (message == null) {
535             write(getMessage(status));
536         } else {
537             write(message, true);
538         }
539 
540         // End the response status line
541         if (System.getSecurityManager() != null) {
542             AccessController.doPrivileged(
543                     new PrivilegedAction() {
544                         public Object run() {
545                             write(Constants.CRLF_BYTES);
546                             return null;
547                         }
548                     }
549             );
550         } else {
551             write(Constants.CRLF_BYTES);
552         }
553 
554     }
555 
556     private String getMessage(final int message) {
557         if (System.getSecurityManager() != null) {
558             return (String) AccessController.<String>doPrivileged(
559                     new PrivilegedAction<String>() {
560                         public String run() {
561                             return HttpMessages.getMessage(message);
562                         }
563                     }
564             );
565         } else {
566             return HttpMessages.getMessage(message);
567         }
568     }
569 
570     /**
571      * Send a header.
572      *
573      * @param name  Header name
574      * @param value Header value
575      */
576     public void sendHeader(MessageBytes name, MessageBytes value) {
577 
578         write(name);
579         write(": ");
580         write(value);
581         write(Constants.CRLF_BYTES);
582 
583     }
584 
585 
586     /**
587      * Send a header.
588      *
589      * @param name  Header name
590      * @param value Header value
591      */
592     public void sendHeader(ByteChunk name, ByteChunk value) {
593 
594         write(name);
595         write(": ");
596         write(value);
597         write(Constants.CRLF_BYTES);
598 
599     }
600 
601 
602     /**
603      * Send a header.
604      *
605      * @param name  Header name
606      * @param value Header value
607      */
608     public void sendHeader(String name, String value) {
609 
610         write(name);
611         write(": ");
612         write(value);
613         write(Constants.CRLF_BYTES);
614 
615     }
616 
617 
618     /**
619      * End the header block.
620      */
621     public void endHeaders() {
622 
623         write(Constants.CRLF_BYTES);
624 
625     }
626 
627 
628     // --------------------------------------------------- OutputBuffer Methods
629 
630 
631     /**
632      * Write the contents of a byte chunk.
633      *
634      * @param chunk byte chunk
635      * @return number of bytes written
636      * @throws IOException an undelying I/O error occured
637      */
638     public int doWrite(ByteChunk chunk, Response res)
639             throws IOException {
640 
641         if (response.isSuspended()) {
642             response.action(ActionCode.RESET_SUSPEND_TIMEOUT, null);
643         }
644 
645         if (!committed) {
646 
647             // Send the connector a request for commit. The connector should
648             // then validate the headers, send them (using sendHeaders) and 
649             // set the filters accordingly.
650             response.action(ActionCode.ACTION_COMMIT, null);
651 
652         }
653 
654         if (lastOutputFilter != null && activeFilters[0] != lastOutputFilter) {
655             addActiveFilter(lastOutputFilter);
656         }
657 
658         if (lastActiveFilter == -1) {
659             return outputStreamOutputBuffer.doWrite(chunk, res);
660         } else {
661             return activeFilters[lastActiveFilter].doWrite(chunk, res);
662         }
663 
664     }
665 
666 
667     // ------------------------------------------------------ Protected Methods
668 
669 
670     /**
671      * Commit the response.
672      *
673      * @throws IOException an undelying I/O error occured
674      */
675     public void commit()
676             throws IOException {
677 
678         // The response is now committed
679         committed = true;
680         response.setCommitted(true);
681 
682         /* GlassFish Issue 646
683         if (pos > 0) {
684             // Sending the response header buffer
685             if (useSocketBuffer) {
686                 socketBuffer.append(buf, 0, pos);
687             } else {
688                 outputStream.write(buf, 0, pos);
689             }
690             flush(false);
691         }*/
692         // START GlassFish Issue 646
693         if (pos > 0) {
694             flush(false);
695         }
696         // END GlassFish Issue 646
697 
698     }
699 
700 
701     /**
702      * This method will write the contents of the specyfied message bytes
703      * buffer to the output stream, without filtering. This method is meant to
704      * be used to write the response header.
705      *
706      * @param mb data to be written
707      */
708     protected void write(MessageBytes mb) {
709 
710         if (mb.getType() == MessageBytes.T_BYTES) {
711             ByteChunk bc = mb.getByteChunk();
712             write(bc);
713         } else if (mb.getType() == MessageBytes.T_CHARS) {
714             CharChunk cc = mb.getCharChunk();
715             write(cc);
716         } else {
717             write(mb.toString());
718         }
719 
720     }
721 
722 
723     /**
724      * This method will write the contents of the specyfied message bytes
725      * buffer to the output stream, without filtering. This method is meant to
726      * be used to write the response header.
727      *
728      * @param bc data to be written
729      */
730     protected void write(ByteChunk bc) {
731         try {
732             realWriteBytes(bc.getBytes(), bc.getStart(), bc.getLength());
733         } catch (IOException ex) {
734         }
735     }
736 
737 
738     /**
739      * This method will write the contents of the specified char
740      * buffer to the output stream, without filtering. This method is meant to
741      * be used to write the response header.
742      *
743      * @param cc data to be written
744      */
745     protected void write(CharChunk cc) {
746 
747         int start = cc.getStart();
748         int end = cc.getEnd();
749         char[] cbuf = cc.getBuffer();
750         for (int i = start; i < end; i++) {
751             char c = cbuf[i];
752             if ((c <= 31 && c != 9) || c == 127 || c > 255) {
753                 c = ' ';
754             }
755 
756             if (pos >= buf.length) {
757                 try {
758                     flush(true);
759                 } catch (IOException e) {
760                 }
761             }
762 
763             buf[pos++] = (byte) c;
764         }
765 
766         try {
767             flush(true);
768         } catch (IOException ex) {
769         }
770     }
771 
772 
773     /**
774      * This method will write the contents of the specyfied byte
775      * buffer to the output stream, without filtering. This method is meant to
776      * be used to write the response header.
777      *
778      * @param b data to be written
779      */
780     protected void write(byte[] b) {
781         try {
782             realWriteBytes(b, 0, b.length);
783         } catch (IOException ex) {
784         }
785     }
786 
787 
788     /**
789      * This method will write the contents of the specyfied String to the
790      * output stream, without filtering. This method is meant to be used to
791      * write the response header.
792      *
793      * @param s data to be written
794      */
795     protected void write(String s) {
796         write(s, false);
797     }
798 
799 
800     /**
801      * This method will write the contents of the specyfied String to the
802      * output stream, without filtering. This method is meant to be used to
803      * write the response header.
804      *
805      * @param s             data to be written
806      * @param replacingCRLF replacing char with lower byte that is \n or \r
807      */
808     protected void write(String s, boolean replacingCRLF) {
809 
810         if (s == null) {
811             return;
812         }
813 
814         // From the Tomcat 3.3 HTTP/1.0 connector
815         int len = s.length();
816         for (int i = 0; i < len; i++) {
817             char c = s.charAt(i);
818             // Note:  This is clearly incorrect for many strings,
819             // but is the only consistent approach within the current
820             // servlet framework.  It must suffice until servlet output
821             // streams properly encode their output.
822             if ((c <= 31 && c != 9) || c == 127 || c > 255) {
823                 c = ' ';
824             }
825 
826             byte b = (byte) c;
827             if (replacingCRLF && (b == 10 || b == 13)) {  // \n or \r
828                 b = 32; // space
829             }
830 
831             if (pos >= buf.length) {
832                 try {
833                     flush(true);
834                 } catch (IOException e) {
835                 }
836             }
837 
838             buf[pos++] = b;
839         }
840 
841         try {
842             flush(true);
843         } catch (IOException ex) {
844         }
845     }
846 
847 
848     /**
849      * This method will print the specified integer to the output stream,
850      * without filtering. This method is meant to be used to write the
851      * response header.
852      *
853      * @param i data to be written
854      */
855     protected void write(int i) {
856 
857         write(String.valueOf(i));
858 
859     }
860 
861 
862     /**
863      * Callback to write data from the buffer.
864      */
865     public void realWriteBytes(byte cbuf[], int off, int len)
866             throws IOException {
867         if (len > 0) {
868             if (useSocketBuffer) {
869                 socketBuffer.append(cbuf, off, len);
870             } else {
871                 outputStream.write(cbuf, off, len);
872             }
873         }
874     }
875 
876 
877     // ----------------------------------- OutputStreamOutputBuffer Inner Class
878 
879 
880     /**
881      * This class is an output buffer which will write data to an output
882      * stream.
883      */
884     public class OutputStreamOutputBuffer
885             implements OutputBuffer {
886 
887 
888         /**
889          * Write chunk.
890          */
891         public int doWrite(ByteChunk chunk, Response res)
892                 throws IOException {
893 
894             realWriteBytes(chunk.getBuffer(), chunk.getStart(),
895                     chunk.getLength());
896             return chunk.getLength();
897         }
898 
899 
900     }
901 
902 
903 }