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 package org.apache.catalina.connector;
18
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.nio.Buffer;
22 import java.nio.ByteBuffer;
23 import java.nio.CharBuffer;
24 import java.nio.charset.Charset;
25 import java.security.AccessController;
26 import java.security.PrivilegedActionException;
27 import java.security.PrivilegedExceptionAction;
28 import java.util.HashMap;
29 import java.util.Map;
30
31 import javax.servlet.WriteListener;
32 import javax.servlet.http.HttpServletResponse;
33
34 import org.apache.catalina.Globals;
35 import org.apache.coyote.ActionCode;
36 import org.apache.coyote.CloseNowException;
37 import org.apache.coyote.Response;
38 import org.apache.tomcat.util.buf.C2BConverter;
39 import org.apache.tomcat.util.res.StringManager;
40
41 /**
42 * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
43 * OutputBuffer, with the removal of some of the state handling (which in
44 * Coyote is mostly the Processor's responsibility).
45 *
46 * @author Costin Manolache
47 * @author Remy Maucherat
48 */
49 public class OutputBuffer extends Writer {
50
51 private static final StringManager sm = StringManager.getManager(OutputBuffer.class);
52
53 public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
54
55 /**
56 * Encoder cache.
57 */
58 private final Map<Charset, C2BConverter> encoders = new HashMap<>();
59
60
61 /**
62 * Default buffer size.
63 */
64 private final int defaultBufferSize;
65
66 // ----------------------------------------------------- Instance Variables
67
68 /**
69 * The byte buffer.
70 */
71 private ByteBuffer bb;
72
73
74 /**
75 * The char buffer.
76 */
77 private final CharBuffer cb;
78
79
80 /**
81 * State of the output buffer.
82 */
83 private boolean initial = true;
84
85
86 /**
87 * Number of bytes written.
88 */
89 private long bytesWritten = 0;
90
91
92 /**
93 * Number of chars written.
94 */
95 private long charsWritten = 0;
96
97
98 /**
99 * Flag which indicates if the output buffer is closed.
100 */
101 private volatile boolean closed = false;
102
103
104 /**
105 * Do a flush on the next operation.
106 */
107 private boolean doFlush = false;
108
109
110 /**
111 * Current char to byte converter.
112 */
113 protected C2BConverter conv;
114
115
116 /**
117 * Associated Coyote response.
118 */
119 private Response coyoteResponse;
120
121
122 /**
123 * Suspended flag. All output bytes will be swallowed if this is true.
124 */
125 private volatile boolean suspended = false;
126
127
128 // ----------------------------------------------------------- Constructors
129
130 /**
131 * Create the buffer with the specified initial size.
132 *
133 * @param size Buffer size to use
134 */
135 public OutputBuffer(int size) {
136 defaultBufferSize = size;
137 bb = ByteBuffer.allocate(size);
138 clear(bb);
139 cb = CharBuffer.allocate(size);
140 clear(cb);
141 }
142
143
144 // ------------------------------------------------------------- Properties
145
146 /**
147 * Associated Coyote response.
148 *
149 * @param coyoteResponse Associated Coyote response
150 */
151 public void setResponse(Response coyoteResponse) {
152 this.coyoteResponse = coyoteResponse;
153 }
154
155
156 /**
157 * Is the response output suspended ?
158 *
159 * @return suspended flag value
160 */
161 public boolean isSuspended() {
162 return this.suspended;
163 }
164
165
166 /**
167 * Set the suspended flag.
168 *
169 * @param suspended New suspended flag value
170 */
171 public void setSuspended(boolean suspended) {
172 this.suspended = suspended;
173 }
174
175
176 /**
177 * Is the response output closed ?
178 *
179 * @return closed flag value
180 */
181 public boolean isClosed() {
182 return this.closed;
183 }
184
185
186 // --------------------------------------------------------- Public Methods
187
188 /**
189 * Recycle the output buffer.
190 */
191 public void recycle() {
192
193 initial = true;
194 bytesWritten = 0;
195 charsWritten = 0;
196
197 if (bb.capacity() > 16 * defaultBufferSize) {
198 // Discard buffers which are too large
199 bb = ByteBuffer.allocate(defaultBufferSize);
200 }
201 clear(bb);
202 clear(cb);
203 closed = false;
204 suspended = false;
205 doFlush = false;
206
207 if (conv != null) {
208 conv.recycle();
209 conv = null;
210 }
211 }
212
213
214 /**
215 * Close the output buffer. This tries to calculate the response size if
216 * the response has not been committed yet.
217 *
218 * @throws IOException An underlying IOException occurred
219 */
220 @Override
221 public void close() throws IOException {
222
223 if (closed) {
224 return;
225 }
226 if (suspended) {
227 return;
228 }
229
230 // If there are chars, flush all of them to the byte buffer now as bytes are used to
231 // calculate the content-length (if everything fits into the byte buffer, of course).
232 if (cb.remaining() > 0) {
233 flushCharBuffer();
234 }
235
236 if ((!coyoteResponse.isCommitted()) && (coyoteResponse.getContentLengthLong() == -1)
237 && !coyoteResponse.getRequest().method().equals("HEAD")) {
238 // If this didn't cause a commit of the response, the final content
239 // length can be calculated. Only do this if this is not a HEAD
240 // request since in that case no body should have been written and
241 // setting a value of zero here will result in an explicit content
242 // length of zero being set on the response.
243 if (!coyoteResponse.isCommitted()) {
244 coyoteResponse.setContentLength(bb.remaining());
245 }
246 }
247
248 if (coyoteResponse.getStatus() == HttpServletResponse.SC_SWITCHING_PROTOCOLS) {
249 doFlush(true);
250 } else {
251 doFlush(false);
252 }
253 closed = true;
254
255 // The request should have been completely read by the time the response
256 // is closed. Further reads of the input a) are pointless and b) really
257 // confuse AJP (bug 50189) so close the input buffer to prevent them.
258 Request req = (Request) coyoteResponse.getRequest().getNote(CoyoteAdapter.ADAPTER_NOTES);
259 req.inputBuffer.close();
260
261 coyoteResponse.action(ActionCode.CLOSE, null);
262 }
263
264
265 /**
266 * Flush bytes or chars contained in the buffer.
267 *
268 * @throws IOException An underlying IOException occurred
269 */
270 @Override
271 public void flush() throws IOException {
272 doFlush(true);
273 }
274
275
276 /**
277 * Flush bytes or chars contained in the buffer.
278 *
279 * @param realFlush <code>true</code> if this should also cause a real network flush
280 * @throws IOException An underlying IOException occurred
281 */
282 protected void doFlush(boolean realFlush) throws IOException {
283
284 if (suspended) {
285 return;
286 }
287
288 try {
289 doFlush = true;
290 if (initial) {
291 coyoteResponse.sendHeaders();
292 initial = false;
293 }
294 if (cb.remaining() > 0) {
295 flushCharBuffer();
296 }
297 if (bb.remaining() > 0) {
298 flushByteBuffer();
299 }
300 } finally {
301 doFlush = false;
302 }
303
304 if (realFlush) {
305 coyoteResponse.action(ActionCode.CLIENT_FLUSH, null);
306 // If some exception occurred earlier, or if some IOE occurred
307 // here, notify the servlet with an IOE
308 if (coyoteResponse.isExceptionPresent()) {
309 throw new ClientAbortException(coyoteResponse.getErrorException());
310 }
311 }
312
313 }
314
315
316 // ------------------------------------------------- Bytes Handling Methods
317
318 /**
319 * Sends the buffer data to the client output, checking the
320 * state of Response and calling the right interceptors.
321 *
322 * @param buf the ByteBuffer to be written to the response
323 *
324 * @throws IOException An underlying IOException occurred
325 */
326 public void realWriteBytes(ByteBuffer buf) throws IOException {
327
328 if (closed) {
329 return;
330 }
331 if (coyoteResponse == null) {
332 return;
333 }
334
335 // If we really have something to write
336 if (buf.remaining() > 0) {
337 // real write to the adapter
338 try {
339 coyoteResponse.doWrite(buf);
340 } catch (CloseNowException e) {
341 // Catch this sub-class as it requires specific handling.
342 // Examples where this exception is thrown:
343 // - HTTP/2 stream timeout
344 // Prevent further output for this response
345 closed = true;
346 throw e;
347 } catch (IOException e) {
348 // An IOException on a write is almost always due to
349 // the remote client aborting the request. Wrap this
350 // so that it can be handled better by the error dispatcher.
351 throw new ClientAbortException(e);
352 }
353 }
354
355 }
356
357
358 public void write(byte b[], int off, int len) throws IOException {
359
360 if (suspended) {
361 return;
362 }
363
364 writeBytes(b, off, len);
365
366 }
367
368
369 public void write(ByteBuffer from) throws IOException {
370
371 if (suspended) {
372 return;
373 }
374
375 writeBytes(from);
376
377 }
378
379
380 private void writeBytes(byte b[], int off, int len) throws IOException {
381
382 if (closed) {
383 return;
384 }
385
386 append(b, off, len);
387 bytesWritten += len;
388
389 // if called from within flush(), then immediately flush
390 // remaining bytes
391 if (doFlush) {
392 flushByteBuffer();
393 }
394
395 }
396
397
398 private void writeBytes(ByteBuffer from) throws IOException {
399
400 if (closed) {
401 return;
402 }
403
404 append(from);
405 bytesWritten += from.remaining();
406
407 // if called from within flush(), then immediately flush
408 // remaining bytes
409 if (doFlush) {
410 flushByteBuffer();
411 }
412
413 }
414
415
416 public void writeByte(int b) throws IOException {
417
418 if (suspended) {
419 return;
420 }
421
422 if (isFull(bb)) {
423 flushByteBuffer();
424 }
425
426 transfer((byte) b, bb);
427 bytesWritten++;
428
429 }
430
431
432 // ------------------------------------------------- Chars Handling Methods
433
434
435 /**
436 * Convert the chars to bytes, then send the data to the client.
437 *
438 * @param from Char buffer to be written to the response
439 *
440 * @throws IOException An underlying IOException occurred
441 */
442 public void realWriteChars(CharBuffer from) throws IOException {
443
444 while (from.remaining() > 0) {
445 conv.convert(from, bb);
446 if (bb.remaining() == 0) {
447 // Break out of the loop if more chars are needed to produce any output
448 break;
449 }
450 if (from.remaining() > 0) {
451 flushByteBuffer();
452 } else if (conv.isUndeflow() && bb.limit() > bb.capacity() - 4) {
453 // Handle an edge case. There are no more chars to write at the
454 // moment but there is a leftover character in the converter
455 // which must be part of a surrogate pair. The byte buffer does
456 // not have enough space left to output the bytes for this pair
457 // once it is complete )it will require 4 bytes) so flush now to
458 // prevent the bytes for the leftover char and the rest of the
459 // surrogate pair yet to be written from being lost.
460 // See TestOutputBuffer#testUtf8SurrogateBody()
461 flushByteBuffer();
462 }
463 }
464
465 }
466
467 @Override
468 public void write(int c) throws IOException {
469
470 if (suspended) {
471 return;
472 }
473
474 if (isFull(cb)) {
475 flushCharBuffer();
476 }
477
478 transfer((char) c, cb);
479 charsWritten++;
480
481 }
482
483
484 @Override
485 public void write(char c[]) throws IOException {
486
487 if (suspended) {
488 return;
489 }
490
491 write(c, 0, c.length);
492
493 }
494
495
496 @Override
497 public void write(char c[], int off, int len) throws IOException {
498
499 if (suspended) {
500 return;
501 }
502
503 append(c, off, len);
504 charsWritten += len;
505
506 }
507
508
509 /**
510 * Append a string to the buffer
511 */
512 @Override
513 public void write(String s, int off, int len) throws IOException {
514
515 if (suspended) {
516 return;
517 }
518
519 if (s == null) {
520 throw new NullPointerException(sm.getString("outputBuffer.writeNull"));
521 }
522
523 int sOff = off;
524 int sEnd = off + len;
525 while (sOff < sEnd) {
526 int n = transfer(s, sOff, sEnd - sOff, cb);
527 sOff += n;
528 if (isFull(cb)) {
529 flushCharBuffer();
530 }
531 }
532
533 charsWritten += len;
534 }
535
536
537 @Override
538 public void write(String s) throws IOException {
539
540 if (suspended) {
541 return;
542 }
543
544 if (s == null) {
545 s = "null";
546 }
547 write(s, 0, s.length());
548 }
549
550
551 public void checkConverter() throws IOException {
552 if (conv != null) {
553 return;
554 }
555
556 Charset charset = null;
557
558 if (coyoteResponse != null) {
559 charset = coyoteResponse.getCharset();
560 }
561
562 if (charset == null) {
563 charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
564 }
565
566 conv = encoders.get(charset);
567
568 if (conv == null) {
569 conv = createConverter(charset);
570 encoders.put(charset, conv);
571 }
572 }
573
574
575 private static C2BConverter createConverter(final Charset charset) throws IOException {
576 if (Globals.IS_SECURITY_ENABLED) {
577 try {
578 return AccessController.doPrivileged(new PrivilegedCreateConverter(charset));
579 } catch (PrivilegedActionException ex) {
580 Exception e = ex.getException();
581 if (e instanceof IOException) {
582 throw (IOException) e;
583 } else {
584 throw new IOException(ex);
585 }
586 }
587 } else {
588 return new C2BConverter(charset);
589 }
590 }
591
592
593 // -------------------- BufferedOutputStream compatibility
594
595 public long getContentWritten() {
596 return bytesWritten + charsWritten;
597 }
598
599 /**
600 * Has this buffer been used at all?
601 *
602 * @return true if no chars or bytes have been added to the buffer since the
603 * last call to {@link #recycle()}
604 */
605 public boolean isNew() {
606 return (bytesWritten == 0) && (charsWritten == 0);
607 }
608
609
610 public void setBufferSize(int size) {
611 if (size > bb.capacity()) {
612 bb = ByteBuffer.allocate(size);
613 clear(bb);
614 }
615 }
616
617
618 public void reset() {
619 reset(false);
620 }
621
622 public void reset(boolean resetWriterStreamFlags) {
623 clear(bb);
624 clear(cb);
625 bytesWritten = 0;
626 charsWritten = 0;
627 if (resetWriterStreamFlags) {
628 if (conv != null) {
629 conv.recycle();
630 }
631 conv = null;
632 }
633 initial = true;
634 }
635
636
637 public int getBufferSize() {
638 return bb.capacity();
639 }
640
641
642 /*
643 * All the non-blocking write state information is held in the Response so
644 * it is visible / accessible to all the code that needs it.
645 */
646
647 public boolean isReady() {
648 return coyoteResponse.isReady();
649 }
650
651
652 public void setWriteListener(WriteListener listener) {
653 coyoteResponse.setWriteListener(listener);
654 }
655
656
657 public boolean isBlocking() {
658 return coyoteResponse.getWriteListener() == null;
659 }
660
661 public void checkRegisterForWrite() {
662 coyoteResponse.checkRegisterForWrite();
663 }
664
665 /**
666 * Add data to the buffer.
667 *
668 * @param src Bytes array
669 * @param off Offset
670 * @param len Length
671 * @throws IOException Writing overflow data to the output channel failed
672 */
673 public void append(byte src[], int off, int len) throws IOException {
674 if (bb.remaining() == 0) {
675 appendByteArray(src, off, len);
676 } else {
677 int n = transfer(src, off, len, bb);
678 len = len - n;
679 off = off + n;
680 if (isFull(bb)) {
681 flushByteBuffer();
682 appendByteArray(src, off, len);
683 }
684 }
685 }
686
687 /**
688 * Add data to the buffer.
689 * @param src Char array
690 * @param off Offset
691 * @param len Length
692 * @throws IOException Writing overflow data to the output channel failed
693 */
694 public void append(char src[], int off, int len) throws IOException {
695 // if we have limit and we're below
696 if(len <= cb.capacity() - cb.limit()) {
697 transfer(src, off, len, cb);
698 return;
699 }
700
701 // Optimization:
702 // If len-avail < length ( i.e. after we fill the buffer with
703 // what we can, the remaining will fit in the buffer ) we'll just
704 // copy the first part, flush, then copy the second part - 1 write
705 // and still have some space for more. We'll still have 2 writes, but
706 // we write more on the first.
707 if(len + cb.limit() < 2 * cb.capacity()) {
708 /* If the request length exceeds the size of the output buffer,
709 flush the output buffer and then write the data directly.
710 We can't avoid 2 writes, but we can write more on the second
711 */
712 int n = transfer(src, off, len, cb);
713
714 flushCharBuffer();
715
716 transfer(src, off + n, len - n, cb);
717 } else {
718 // long write - flush the buffer and write the rest
719 // directly from source
720 flushCharBuffer();
721
722 realWriteChars(CharBuffer.wrap(src, off, len));
723 }
724 }
725
726
727 public void append(ByteBuffer from) throws IOException {
728 if (bb.remaining() == 0) {
729 appendByteBuffer(from);
730 } else {
731 transfer(from, bb);
732 if (isFull(bb)) {
733 flushByteBuffer();
734 appendByteBuffer(from);
735 }
736 }
737 }
738
739 private void appendByteArray(byte src[], int off, int len) throws IOException {
740 if (len == 0) {
741 return;
742 }
743
744 int limit = bb.capacity();
745 while (len >= limit) {
746 realWriteBytes(ByteBuffer.wrap(src, off, limit));
747 len = len - limit;
748 off = off + limit;
749 }
750
751 if (len > 0) {
752 transfer(src, off, len, bb);
753 }
754 }
755
756 private void appendByteBuffer(ByteBuffer from) throws IOException {
757 if (from.remaining() == 0) {
758 return;
759 }
760
761 int limit = bb.capacity();
762 int fromLimit = from.limit();
763 while (from.remaining() >= limit) {
764 from.limit(from.position() + limit);
765 realWriteBytes(from.slice());
766 from.position(from.limit());
767 from.limit(fromLimit);
768 }
769
770 if (from.remaining() > 0) {
771 transfer(from, bb);
772 }
773 }
774
775 private void flushByteBuffer() throws IOException {
776 realWriteBytes(bb.slice());
777 clear(bb);
778 }
779
780 private void flushCharBuffer() throws IOException {
781 realWriteChars(cb.slice());
782 clear(cb);
783 }
784
785 private void transfer(byte b, ByteBuffer to) {
786 toWriteMode(to);
787 to.put(b);
788 toReadMode(to);
789 }
790
791 private void transfer(char b, CharBuffer to) {
792 toWriteMode(to);
793 to.put(b);
794 toReadMode(to);
795 }
796
797 private int transfer(byte[] buf, int off, int len, ByteBuffer to) {
798 toWriteMode(to);
799 int max = Math.min(len, to.remaining());
800 if (max > 0) {
801 to.put(buf, off, max);
802 }
803 toReadMode(to);
804 return max;
805 }
806
807 private int transfer(char[] buf, int off, int len, CharBuffer to) {
808 toWriteMode(to);
809 int max = Math.min(len, to.remaining());
810 if (max > 0) {
811 to.put(buf, off, max);
812 }
813 toReadMode(to);
814 return max;
815 }
816
817 private int transfer(String s, int off, int len, CharBuffer to) {
818 toWriteMode(to);
819 int max = Math.min(len, to.remaining());
820 if (max > 0) {
821 to.put(s, off, off + max);
822 }
823 toReadMode(to);
824 return max;
825 }
826
827 private void transfer(ByteBuffer from, ByteBuffer to) {
828 toWriteMode(to);
829 int max = Math.min(from.remaining(), to.remaining());
830 if (max > 0) {
831 int fromLimit = from.limit();
832 from.limit(from.position() + max);
833 to.put(from);
834 from.limit(fromLimit);
835 }
836 toReadMode(to);
837 }
838
839 private void clear(Buffer buffer) {
840 buffer.rewind().limit(0);
841 }
842
843 private boolean isFull(Buffer buffer) {
844 return buffer.limit() == buffer.capacity();
845 }
846
847 private void toReadMode(Buffer buffer) {
848 buffer.limit(buffer.position())
849 .reset();
850 }
851
852 private void toWriteMode(Buffer buffer) {
853 buffer.mark()
854 .position(buffer.limit())
855 .limit(buffer.capacity());
856 }
857
858
859 private static class PrivilegedCreateConverter
860 implements PrivilegedExceptionAction<C2BConverter> {
861
862 private final Charset charset;
863
864 public PrivilegedCreateConverter(Charset charset) {
865 this.charset = charset;
866 }
867
868 @Override
869 public C2BConverter run() throws IOException {
870 return new C2BConverter(charset);
871 }
872 }
873 }
874