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.catalina.connector;
19
20
21 import java.io.IOException;
22 import java.io.Writer;
23 import java.security.AccessController;
24 import java.security.PrivilegedActionException;
25 import java.security.PrivilegedExceptionAction;
26 import java.util.HashMap;
27
28 import org.apache.coyote.ActionCode;
29 import org.apache.coyote.Response;
30 import org.apache.catalina.Globals;
31 import org.apache.tomcat.util.buf.ByteChunk;
32 import org.apache.tomcat.util.buf.C2BConverter;
33
34
35 /**
36 * The buffer used by Tomcat response. This is a derivative of the Tomcat 3.3
37 * OutputBuffer, with the removal of some of the state handling (which in
38 * Coyote is mostly the Processor's responsability).
39 *
40 * @author Costin Manolache
41 * @author Remy Maucherat
42 */
43 public class OutputBuffer extends Writer
44 implements ByteChunk.ByteOutputChannel {
45
46
47 // -------------------------------------------------------------- Constants
48
49
50 public static final String DEFAULT_ENCODING =
51 org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING;
52 public static final int DEFAULT_BUFFER_SIZE = 8*1024;
53
54
55 // ----------------------------------------------------- Instance Variables
56
57
58 /**
59 * The byte buffer.
60 */
61 private ByteChunk bb;
62
63
64 /**
65 * State of the output buffer.
66 */
67 private boolean initial = true;
68
69
70 /**
71 * Number of bytes written.
72 */
73 private long bytesWritten = 0;
74
75
76 /**
77 * Number of chars written.
78 */
79 private long charsWritten = 0;
80
81
82 /**
83 * Flag which indicates if the output buffer is closed.
84 */
85 private boolean closed = false;
86
87
88 /**
89 * Do a flush on the next operation.
90 */
91 private boolean doFlush = false;
92
93
94 /**
95 * Byte chunk used to output bytes.
96 */
97 private ByteChunk outputChunk = new ByteChunk();
98
99
100 /**
101 * Encoding to use.
102 */
103 private String enc;
104
105
106 /**
107 * Encoder is set.
108 */
109 private boolean gotEnc = false;
110
111
112 /**
113 * List of encoders.
114 */
115 protected HashMap encoders = new HashMap();
116
117
118 /**
119 * Current char to byte converter.
120 */
121 protected C2BConverter conv;
122
123
124 /**
125 * Associated Coyote response.
126 */
127 private Response coyoteResponse;
128
129
130 /**
131 * Suspended flag. All output bytes will be swallowed if this is true.
132 */
133 private boolean suspended = false;
134
135
136 // ----------------------------------------------------------- Constructors
137
138
139 /**
140 * Default constructor. Allocate the buffer with the default buffer size.
141 */
142 public OutputBuffer() {
143
144 this(DEFAULT_BUFFER_SIZE);
145
146 }
147
148
149 /**
150 * Alternate constructor which allows specifying the initial buffer size.
151 *
152 * @param size Buffer size to use
153 */
154 public OutputBuffer(int size) {
155
156 bb = new ByteChunk(size);
157 bb.setLimit(size);
158 bb.setByteOutputChannel(this);
159
160 }
161
162
163 // ------------------------------------------------------------- Properties
164
165
166 /**
167 * Associated Coyote response.
168 *
169 * @param coyoteResponse Associated Coyote response
170 */
171 public void setResponse(Response coyoteResponse) {
172 this.coyoteResponse = coyoteResponse;
173 }
174
175
176 /**
177 * Get associated Coyote response.
178 *
179 * @return the associated Coyote response
180 */
181 public Response getResponse() {
182 return this.coyoteResponse;
183 }
184
185
186 /**
187 * Is the response output suspended ?
188 *
189 * @return suspended flag value
190 */
191 public boolean isSuspended() {
192 return this.suspended;
193 }
194
195
196 /**
197 * Set the suspended flag.
198 *
199 * @param suspended New suspended flag value
200 */
201 public void setSuspended(boolean suspended) {
202 this.suspended = suspended;
203 }
204
205
206 /**
207 * Is the response output closed ?
208 *
209 * @return closed flag value
210 */
211 public boolean isClosed() {
212 return this.closed;
213 }
214
215
216 // --------------------------------------------------------- Public Methods
217
218
219 /**
220 * Recycle the output buffer.
221 */
222 public void recycle() {
223
224 initial = true;
225 bytesWritten = 0;
226 charsWritten = 0;
227
228 bb.recycle();
229 closed = false;
230 suspended = false;
231
232 if (conv!= null) {
233 conv.recycle();
234 }
235
236 gotEnc = false;
237 enc = null;
238
239 }
240
241
242 /**
243 * Clear cached encoders (to save memory for Comet requests).
244 */
245 public void clearEncoders() {
246 encoders.clear();
247 }
248
249
250 /**
251 * Close the output buffer. This tries to calculate the response size if
252 * the response has not been committed yet.
253 *
254 * @throws IOException An underlying IOException occurred
255 */
256 public void close()
257 throws IOException {
258
259 if (closed)
260 return;
261 if (suspended)
262 return;
263
264 if ((!coyoteResponse.isCommitted())
265 && (coyoteResponse.getContentLengthLong() == -1)) {
266 // If this didn't cause a commit of the response, the final content
267 // length can be calculated
268 if (!coyoteResponse.isCommitted()) {
269 coyoteResponse.setContentLength(bb.getLength());
270 }
271 }
272
273 doFlush(false);
274 closed = true;
275
276 coyoteResponse.finish();
277
278 }
279
280
281 /**
282 * Flush bytes or chars contained in the buffer.
283 *
284 * @throws IOException An underlying IOException occurred
285 */
286 public void flush()
287 throws IOException {
288 doFlush(true);
289 }
290
291
292 /**
293 * Flush bytes or chars contained in the buffer.
294 *
295 * @throws IOException An underlying IOException occurred
296 */
297 protected void doFlush(boolean realFlush)
298 throws IOException {
299
300 if (suspended)
301 return;
302
303 doFlush = true;
304 if (initial) {
305 coyoteResponse.sendHeaders();
306 initial = false;
307 }
308 if (bb.getLength() > 0) {
309 bb.flushBuffer();
310 }
311 doFlush = false;
312
313 if (realFlush) {
314 coyoteResponse.action(ActionCode.ACTION_CLIENT_FLUSH,
315 coyoteResponse);
316 // If some exception occurred earlier, or if some IOE occurred
317 // here, notify the servlet with an IOE
318 if (coyoteResponse.isExceptionPresent()) {
319 throw new ClientAbortException
320 (coyoteResponse.getErrorException());
321 }
322 }
323
324 }
325
326
327 // ------------------------------------------------- Bytes Handling Methods
328
329
330 /**
331 * Sends the buffer data to the client output, checking the
332 * state of Response and calling the right interceptors.
333 *
334 * @param buf Byte buffer to be written to the response
335 * @param off Offset
336 * @param cnt Length
337 *
338 * @throws IOException An underlying IOException occurred
339 */
340 public void realWriteBytes(byte buf[], int off, int cnt)
341 throws IOException {
342
343 if (closed)
344 return;
345 if (coyoteResponse == null)
346 return;
347
348 // If we really have something to write
349 if (cnt > 0) {
350 // real write to the adapter
351 outputChunk.setBytes(buf, off, cnt);
352 try {
353 coyoteResponse.doWrite(outputChunk);
354 } catch (IOException e) {
355 // An IOException on a write is almost always due to
356 // the remote client aborting the request. Wrap this
357 // so that it can be handled better by the error dispatcher.
358 throw new ClientAbortException(e);
359 }
360 }
361
362 }
363
364
365 public void write(byte b[], int off, int len) throws IOException {
366
367 if (suspended)
368 return;
369
370 writeBytes(b, off, len);
371
372 }
373
374
375 private void writeBytes(byte b[], int off, int len)
376 throws IOException {
377
378 if (closed)
379 return;
380
381 bb.append(b, off, len);
382 bytesWritten += len;
383
384 // if called from within flush(), then immediately flush
385 // remaining bytes
386 if (doFlush) {
387 bb.flushBuffer();
388 }
389
390 }
391
392
393 public void writeByte(int b)
394 throws IOException {
395
396 if (suspended)
397 return;
398
399 bb.append((byte) b);
400 bytesWritten++;
401
402 }
403
404
405 // ------------------------------------------------- Chars Handling Methods
406
407
408 public void write(int c)
409 throws IOException {
410
411 if (suspended)
412 return;
413
414 conv.convert((char) c);
415 conv.flushBuffer();
416 charsWritten++;
417
418 }
419
420
421 public void write(char c[])
422 throws IOException {
423
424 if (suspended)
425 return;
426
427 write(c, 0, c.length);
428
429 }
430
431
432 public void write(char c[], int off, int len)
433 throws IOException {
434
435 if (suspended)
436 return;
437
438 conv.convert(c, off, len);
439 conv.flushBuffer();
440 charsWritten += len;
441
442 }
443
444
445 /**
446 * Append a string to the buffer
447 */
448 public void write(String s, int off, int len)
449 throws IOException {
450
451 if (suspended)
452 return;
453
454 charsWritten += len;
455 if (s == null)
456 s = "null";
457 conv.convert(s, off, len);
458 conv.flushBuffer();
459
460 }
461
462
463 public void write(String s)
464 throws IOException {
465
466 if (suspended)
467 return;
468
469 if (s == null)
470 s = "null";
471 conv.convert(s);
472 conv.flushBuffer();
473
474 }
475
476
477 public void setEncoding(String s) {
478 enc = s;
479 }
480
481
482 public void checkConverter()
483 throws IOException {
484
485 if (!gotEnc)
486 setConverter();
487
488 }
489
490
491 protected void setConverter()
492 throws IOException {
493
494 if (coyoteResponse != null)
495 enc = coyoteResponse.getCharacterEncoding();
496
497 gotEnc = true;
498 if (enc == null)
499 enc = DEFAULT_ENCODING;
500 conv = (C2BConverter) encoders.get(enc);
501 if (conv == null) {
502
503 if (Globals.IS_SECURITY_ENABLED){
504 try{
505 conv = (C2BConverter)AccessController.doPrivileged(
506 new PrivilegedExceptionAction(){
507
508 public Object run() throws IOException{
509 return new C2BConverter(bb, enc);
510 }
511
512 }
513 );
514 }catch(PrivilegedActionException ex){
515 Exception e = ex.getException();
516 if (e instanceof IOException)
517 throw (IOException)e;
518 }
519 } else {
520 conv = new C2BConverter(bb, enc);
521 }
522
523 encoders.put(enc, conv);
524
525 }
526 }
527
528
529 // -------------------- BufferedOutputStream compatibility
530
531
532 /**
533 * Real write - this buffer will be sent to the client
534 */
535 public void flushBytes()
536 throws IOException {
537
538 bb.flushBuffer();
539
540 }
541
542 public int getBytesWritten() {
543 if (bytesWritten < Integer.MAX_VALUE) {
544 return (int) bytesWritten;
545 }
546 return -1;
547 }
548
549 public int getCharsWritten() {
550 if (charsWritten < Integer.MAX_VALUE) {
551 return (int) charsWritten;
552 }
553 return -1;
554 }
555
556 public int getContentWritten() {
557 long size = bytesWritten + charsWritten ;
558 if (size < Integer.MAX_VALUE) {
559 return (int) size;
560 }
561 return -1;
562 }
563
564 public long getContentWrittenLong() {
565 return bytesWritten + charsWritten;
566 }
567
568 /**
569 * True if this buffer hasn't been used ( since recycle() ) -
570 * i.e. no chars or bytes have been added to the buffer.
571 */
572 public boolean isNew() {
573 return (bytesWritten == 0) && (charsWritten == 0);
574 }
575
576
577 public void setBufferSize(int size) {
578 if (size > bb.getLimit()) {// ??????
579 bb.setLimit(size);
580 }
581 }
582
583
584 public void reset() {
585
586 bb.recycle();
587 bytesWritten = 0;
588 charsWritten = 0;
589 gotEnc = false;
590 enc = null;
591 initial = true;
592
593 }
594
595
596 public int getBufferSize() {
597 return bb.getLimit();
598 }
599
600
601 }