1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.coyote.http11;
19
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.security.AccessController;
23 import java.security.PrivilegedAction;
24
25 import org.apache.tomcat.util.buf.ByteChunk;
26 import org.apache.tomcat.util.buf.CharChunk;
27 import org.apache.tomcat.util.buf.MessageBytes;
28 import org.apache.tomcat.util.http.HttpMessages;
29 import org.apache.tomcat.util.http.MimeHeaders;
30 import org.apache.tomcat.util.res.StringManager;
31
32 import org.apache.coyote.ActionCode;
33 import org.apache.coyote.OutputBuffer;
34 import org.apache.coyote.Response;
35
36 /**
37 * Output buffer.
38 *
39 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
40 */
41 public class InternalOutputBuffer
42 implements OutputBuffer, ByteChunk.ByteOutputChannel {
43
44 // -------------------------------------------------------------- Constants
45
46
47 // ----------------------------------------------------------- Constructors
48
49
50 /**
51 * Default constructor.
52 */
53 public InternalOutputBuffer(Response response) {
54 this(response, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
55 }
56
57
58 /**
59 * Alternate constructor.
60 */
61 public InternalOutputBuffer(Response response, int headerBufferSize) {
62
63 this.response = response;
64
65 headers = response.getMimeHeaders();
66
67 buf = new byte[headerBufferSize];
68
69 outputStreamOutputBuffer = new OutputStreamOutputBuffer();
70
71 filterLibrary = new OutputFilter[0];
72 activeFilters = new OutputFilter[0];
73 lastActiveFilter = -1;
74
75 socketBuffer = new ByteChunk();
76 socketBuffer.setByteOutputChannel(this);
77
78 committed = false;
79 finished = false;
80
81 }
82
83
84 // -------------------------------------------------------------- Variables
85
86
87 /**
88 * The string manager for this package.
89 */
90 protected static StringManager sm =
91 StringManager.getManager(Constants.Package);
92
93
94 // ----------------------------------------------------- Instance Variables
95
96
97 /**
98 * Associated Coyote response.
99 */
100 protected Response response;
101
102
103 /**
104 * Headers of the associated request.
105 */
106 protected MimeHeaders headers;
107
108
109 /**
110 * Committed flag.
111 */
112 protected boolean committed;
113
114
115 /**
116 * Finished flag.
117 */
118 protected boolean finished;
119
120
121 /**
122 * The buffer used for header composition.
123 */
124 protected byte[] buf;
125
126
127 /**
128 * Position in the buffer.
129 */
130 protected int pos;
131
132
133 /**
134 * Underlying output stream.
135 */
136 protected OutputStream outputStream;
137
138
139 /**
140 * Underlying output buffer.
141 */
142 protected OutputBuffer outputStreamOutputBuffer;
143
144
145 /**
146 * Filter library.
147 * Note: Filter[0] is always the "chunked" filter.
148 */
149 protected OutputFilter[] filterLibrary;
150
151
152 /**
153 * Active filter (which is actually the top of the pipeline).
154 */
155 protected OutputFilter[] activeFilters;
156
157
158 /**
159 * Index of the last active filter.
160 */
161 protected int lastActiveFilter;
162
163
164 /**
165 * Socket buffer.
166 */
167 protected ByteChunk socketBuffer;
168
169
170 /**
171 * Socket buffer (extra buffering to reduce number of packets sent).
172 */
173 protected boolean useSocketBuffer = false;
174
175
176 // ------------------------------------------------------------- Properties
177
178
179 /**
180 * Set the underlying socket output stream.
181 */
182 public void setOutputStream(OutputStream outputStream) {
183
184 // FIXME: Check for null ?
185
186 this.outputStream = outputStream;
187
188 }
189
190
191 /**
192 * Get the underlying socket output stream.
193 */
194 public OutputStream getOutputStream() {
195
196 return outputStream;
197
198 }
199
200
201 /**
202 * Set the socket buffer size.
203 */
204 public void setSocketBuffer(int socketBufferSize) {
205
206 if (socketBufferSize > 500) {
207 useSocketBuffer = true;
208 socketBuffer.allocate(socketBufferSize, socketBufferSize);
209 } else {
210 useSocketBuffer = false;
211 }
212
213 }
214
215
216 /**
217 * Add an output filter to the filter library.
218 */
219 public void addFilter(OutputFilter filter) {
220
221 OutputFilter[] newFilterLibrary =
222 new OutputFilter[filterLibrary.length + 1];
223 for (int i = 0; i < filterLibrary.length; i++) {
224 newFilterLibrary[i] = filterLibrary[i];
225 }
226 newFilterLibrary[filterLibrary.length] = filter;
227 filterLibrary = newFilterLibrary;
228
229 activeFilters = new OutputFilter[filterLibrary.length];
230
231 }
232
233
234 /**
235 * Get filters.
236 */
237 public OutputFilter[] getFilters() {
238
239 return filterLibrary;
240
241 }
242
243
244 /**
245 * Clear filters.
246 */
247 public void clearFilters() {
248
249 filterLibrary = new OutputFilter[0];
250 lastActiveFilter = -1;
251
252 }
253
254
255 /**
256 * Add an output filter to the filter library.
257 */
258 public void addActiveFilter(OutputFilter filter) {
259
260 if (lastActiveFilter == -1) {
261 filter.setBuffer(outputStreamOutputBuffer);
262 } else {
263 for (int i = 0; i <= lastActiveFilter; i++) {
264 if (activeFilters[i] == filter)
265 return;
266 }
267 filter.setBuffer(activeFilters[lastActiveFilter]);
268 }
269
270 activeFilters[++lastActiveFilter] = filter;
271
272 filter.setResponse(response);
273
274 }
275
276
277 // --------------------------------------------------------- Public Methods
278
279
280 /**
281 * Flush the response.
282 *
283 * @throws IOException an undelying I/O error occured
284 */
285 public void flush()
286 throws IOException {
287
288 if (!committed) {
289
290 // Send the connector a request for commit. The connector should
291 // then validate the headers, send them (using sendHeader) and
292 // set the filters accordingly.
293 response.action(ActionCode.ACTION_COMMIT, null);
294
295 }
296
297 // Flush the current buffer
298 if (useSocketBuffer) {
299 socketBuffer.flushBuffer();
300 }
301
302 }
303
304
305 /**
306 * Reset current response.
307 *
308 * @throws IllegalStateException if the response has already been committed
309 */
310 public void reset() {
311
312 if (committed)
313 throw new IllegalStateException(/*FIXME:Put an error message*/);
314
315 // Recycle Request object
316 response.recycle();
317
318 }
319
320
321 /**
322 * Recycle the output buffer. This should be called when closing the
323 * connection.
324 */
325 public void recycle() {
326
327 // Recycle Request object
328 response.recycle();
329 socketBuffer.recycle();
330
331 outputStream = null;
332 pos = 0;
333 lastActiveFilter = -1;
334 committed = false;
335 finished = false;
336
337 }
338
339
340 /**
341 * End processing of current HTTP request.
342 * Note: All bytes of the current request should have been already
343 * consumed. This method only resets all the pointers so that we are ready
344 * to parse the next HTTP request.
345 */
346 public void nextRequest() {
347
348 // Recycle Request object
349 response.recycle();
350 socketBuffer.recycle();
351
352 // Recycle filters
353 for (int i = 0; i <= lastActiveFilter; i++) {
354 activeFilters[i].recycle();
355 }
356
357 // Reset pointers
358 pos = 0;
359 lastActiveFilter = -1;
360 committed = false;
361 finished = false;
362
363 }
364
365
366 /**
367 * End request.
368 *
369 * @throws IOException an undelying I/O error occured
370 */
371 public void endRequest()
372 throws IOException {
373
374 if (!committed) {
375
376 // Send the connector a request for commit. The connector should
377 // then validate the headers, send them (using sendHeader) and
378 // set the filters accordingly.
379 response.action(ActionCode.ACTION_COMMIT, null);
380
381 }
382
383 if (finished)
384 return;
385
386 if (lastActiveFilter != -1)
387 activeFilters[lastActiveFilter].end();
388
389 if (useSocketBuffer) {
390 socketBuffer.flushBuffer();
391 }
392
393 finished = true;
394
395 }
396
397
398 // ------------------------------------------------ HTTP/1.1 Output Methods
399
400
401 /**
402 * Send an acknoledgement.
403 */
404 public void sendAck()
405 throws IOException {
406
407 if (!committed)
408 outputStream.write(Constants.ACK_BYTES);
409
410 }
411
412
413 /**
414 * Send the response status line.
415 */
416 public void sendStatus() {
417
418 // Write protocol name
419 write(Constants.HTTP_11_BYTES);
420 buf[pos++] = Constants.SP;
421
422 // Write status code
423 int status = response.getStatus();
424 switch (status) {
425 case 200:
426 write(Constants._200_BYTES);
427 break;
428 case 400:
429 write(Constants._400_BYTES);
430 break;
431 case 404:
432 write(Constants._404_BYTES);
433 break;
434 default:
435 write(status);
436 }
437
438 buf[pos++] = Constants.SP;
439
440 // Write message
441 String message = response.getMessage();
442 if (message == null) {
443 write(getMessage(status));
444 } else {
445 write(message);
446 }
447
448 // End the response status line
449 if (org.apache.coyote.Constants.IS_SECURITY_ENABLED){
450 AccessController.doPrivileged(
451 new PrivilegedAction(){
452 public Object run(){
453 buf[pos++] = Constants.CR;
454 buf[pos++] = Constants.LF;
455 return null;
456 }
457 }
458 );
459 } else {
460 buf[pos++] = Constants.CR;
461 buf[pos++] = Constants.LF;
462 }
463
464 }
465
466 private String getMessage(final int message){
467 if (org.apache.coyote.Constants.IS_SECURITY_ENABLED){
468 return (String)AccessController.doPrivileged(
469 new PrivilegedAction(){
470 public Object run(){
471 return HttpMessages.getMessage(message);
472 }
473 }
474 );
475 } else {
476 return HttpMessages.getMessage(message);
477 }
478 }
479
480 /**
481 * Send a header.
482 *
483 * @param name Header name
484 * @param value Header value
485 */
486 public void sendHeader(MessageBytes name, MessageBytes value) {
487
488 write(name);
489 buf[pos++] = Constants.COLON;
490 buf[pos++] = Constants.SP;
491 write(value);
492 buf[pos++] = Constants.CR;
493 buf[pos++] = Constants.LF;
494
495 }
496
497
498 /**
499 * Send a header.
500 *
501 * @param name Header name
502 * @param value Header value
503 */
504 public void sendHeader(ByteChunk name, ByteChunk value) {
505
506 write(name);
507 buf[pos++] = Constants.COLON;
508 buf[pos++] = Constants.SP;
509 write(value);
510 buf[pos++] = Constants.CR;
511 buf[pos++] = Constants.LF;
512
513 }
514
515
516 /**
517 * Send a header.
518 *
519 * @param name Header name
520 * @param value Header value
521 */
522 public void sendHeader(String name, String value) {
523
524 write(name);
525 buf[pos++] = Constants.COLON;
526 buf[pos++] = Constants.SP;
527 write(value);
528 buf[pos++] = Constants.CR;
529 buf[pos++] = Constants.LF;
530
531 }
532
533
534 /**
535 * End the header block.
536 */
537 public void endHeaders() {
538
539 buf[pos++] = Constants.CR;
540 buf[pos++] = Constants.LF;
541
542 }
543
544
545 // --------------------------------------------------- OutputBuffer Methods
546
547
548 /**
549 * Write the contents of a byte chunk.
550 *
551 * @param chunk byte chunk
552 * @return number of bytes written
553 * @throws IOException an undelying I/O error occured
554 */
555 public int doWrite(ByteChunk chunk, Response res)
556 throws IOException {
557
558 if (!committed) {
559
560 // Send the connector a request for commit. The connector should
561 // then validate the headers, send them (using sendHeaders) and
562 // set the filters accordingly.
563 response.action(ActionCode.ACTION_COMMIT, null);
564
565 }
566
567 if (lastActiveFilter == -1)
568 return outputStreamOutputBuffer.doWrite(chunk, res);
569 else
570 return activeFilters[lastActiveFilter].doWrite(chunk, res);
571
572 }
573
574
575 // ------------------------------------------------------ Protected Methods
576
577
578 /**
579 * Commit the response.
580 *
581 * @throws IOException an undelying I/O error occured
582 */
583 protected void commit()
584 throws IOException {
585
586 // The response is now committed
587 committed = true;
588 response.setCommitted(true);
589
590 if (pos > 0) {
591 // Sending the response header buffer
592 if (useSocketBuffer) {
593 socketBuffer.append(buf, 0, pos);
594 } else {
595 outputStream.write(buf, 0, pos);
596 }
597 }
598
599 }
600
601
602 /**
603 * This method will write the contents of the specyfied message bytes
604 * buffer to the output stream, without filtering. This method is meant to
605 * be used to write the response header.
606 *
607 * @param mb data to be written
608 */
609 protected void write(MessageBytes mb) {
610
611 if (mb.getType() == MessageBytes.T_BYTES) {
612 ByteChunk bc = mb.getByteChunk();
613 write(bc);
614 } else if (mb.getType() == MessageBytes.T_CHARS) {
615 CharChunk cc = mb.getCharChunk();
616 write(cc);
617 } else {
618 write(mb.toString());
619 }
620
621 }
622
623
624 /**
625 * This method will write the contents of the specyfied message bytes
626 * buffer to the output stream, without filtering. This method is meant to
627 * be used to write the response header.
628 *
629 * @param bc data to be written
630 */
631 protected void write(ByteChunk bc) {
632
633 // Writing the byte chunk to the output buffer
634 int length = bc.getLength();
635 System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos, length);
636 pos = pos + length;
637
638 }
639
640
641 /**
642 * This method will write the contents of the specyfied char
643 * buffer to the output stream, without filtering. This method is meant to
644 * be used to write the response header.
645 *
646 * @param cc data to be written
647 */
648 protected void write(CharChunk cc) {
649
650 int start = cc.getStart();
651 int end = cc.getEnd();
652 char[] cbuf = cc.getBuffer();
653 for (int i = start; i < end; i++) {
654 char c = cbuf[i];
655 // Note: This is clearly incorrect for many strings,
656 // but is the only consistent approach within the current
657 // servlet framework. It must suffice until servlet output
658 // streams properly encode their output.
659 if ((c <= 31) && (c != 9)) {
660 c = ' ';
661 } else if (c == 127) {
662 c = ' ';
663 }
664 buf[pos++] = (byte) c;
665 }
666
667 }
668
669
670 /**
671 * This method will write the contents of the specyfied byte
672 * buffer to the output stream, without filtering. This method is meant to
673 * be used to write the response header.
674 *
675 * @param b data to be written
676 */
677 public void write(byte[] b) {
678
679 // Writing the byte chunk to the output buffer
680 System.arraycopy(b, 0, buf, pos, b.length);
681 pos = pos + b.length;
682
683 }
684
685
686 /**
687 * This method will write the contents of the specyfied String to the
688 * output stream, without filtering. This method is meant to be used to
689 * write the response header.
690 *
691 * @param s data to be written
692 */
693 protected void write(String s) {
694
695 if (s == null)
696 return;
697
698 // From the Tomcat 3.3 HTTP/1.0 connector
699 int len = s.length();
700 for (int i = 0; i < len; i++) {
701 char c = s.charAt (i);
702 // Note: This is clearly incorrect for many strings,
703 // but is the only consistent approach within the current
704 // servlet framework. It must suffice until servlet output
705 // streams properly encode their output.
706 if ((c <= 31) && (c != 9)) {
707 c = ' ';
708 } else if (c == 127) {
709 c = ' ';
710 }
711 buf[pos++] = (byte) c;
712 }
713
714 }
715
716
717 /**
718 * This method will print the specified integer to the output stream,
719 * without filtering. This method is meant to be used to write the
720 * response header.
721 *
722 * @param i data to be written
723 */
724 protected void write(int i) {
725
726 write(String.valueOf(i));
727
728 }
729
730
731 /**
732 * Callback to write data from the buffer.
733 */
734 public void realWriteBytes(byte cbuf[], int off, int len)
735 throws IOException {
736 if (len > 0) {
737 outputStream.write(cbuf, off, len);
738 }
739 }
740
741
742 // ----------------------------------- OutputStreamOutputBuffer Inner Class
743
744
745 /**
746 * This class is an output buffer which will write data to an output
747 * stream.
748 */
749 protected class OutputStreamOutputBuffer
750 implements OutputBuffer {
751
752
753 /**
754 * Write chunk.
755 */
756 public int doWrite(ByteChunk chunk, Response res)
757 throws IOException {
758
759 int length = chunk.getLength();
760 if (useSocketBuffer) {
761 socketBuffer.append(chunk.getBuffer(), chunk.getStart(),
762 length);
763 } else {
764 outputStream.write(chunk.getBuffer(), chunk.getStart(),
765 length);
766 }
767 return length;
768
769 }
770
771
772 }
773
774
775 }