001: /*
002: * Copyright 1999-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.coyote.http11;
018:
019: import java.io.IOException;
020: import java.io.OutputStream;
021: import java.security.AccessController;
022: import java.security.PrivilegedAction;
023:
024: import org.apache.tomcat.util.buf.ByteChunk;
025: import org.apache.tomcat.util.buf.MessageBytes;
026: import org.apache.tomcat.util.http.HttpMessages;
027: import org.apache.tomcat.util.http.MimeHeaders;
028: import org.apache.tomcat.util.res.StringManager;
029:
030: import org.apache.coyote.ActionCode;
031: import org.apache.coyote.OutputBuffer;
032: import org.apache.coyote.Response;
033:
034: /**
035: * Output buffer.
036: *
037: * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
038: */
039: public class InternalOutputBuffer implements OutputBuffer,
040: ByteChunk.ByteOutputChannel {
041:
042: // -------------------------------------------------------------- Constants
043:
044: // ----------------------------------------------------------- Constructors
045:
046: /**
047: * Default constructor.
048: */
049: public InternalOutputBuffer(Response response) {
050: this (response, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);
051: }
052:
053: /**
054: * Alternate constructor.
055: */
056: public InternalOutputBuffer(Response response, int headerBufferSize) {
057:
058: this .response = response;
059: headers = response.getMimeHeaders();
060:
061: headerBuffer = new byte[headerBufferSize];
062: buf = headerBuffer;
063:
064: outputStreamOutputBuffer = new OutputStreamOutputBuffer();
065:
066: filterLibrary = new OutputFilter[0];
067: activeFilters = new OutputFilter[0];
068: lastActiveFilter = -1;
069:
070: socketBuffer = new ByteChunk();
071: socketBuffer.setByteOutputChannel(this );
072:
073: committed = false;
074: finished = false;
075:
076: }
077:
078: // -------------------------------------------------------------- Variables
079:
080: /**
081: * The string manager for this package.
082: */
083: protected static StringManager sm = StringManager
084: .getManager(Constants.Package);
085:
086: // ----------------------------------------------------- Instance Variables
087:
088: /**
089: * Associated Coyote response.
090: */
091: protected Response response;
092:
093: /**
094: * Headers of the associated request.
095: */
096: protected MimeHeaders headers;
097:
098: /**
099: * Committed flag.
100: */
101: protected boolean committed;
102:
103: /**
104: * Finished flag.
105: */
106: protected boolean finished;
107:
108: /**
109: * Pointer to the current read buffer.
110: */
111: protected byte[] buf;
112:
113: /**
114: * Position in the buffer.
115: */
116: protected int pos;
117:
118: /**
119: * HTTP header buffer.
120: */
121: protected byte[] headerBuffer;
122:
123: /**
124: * Underlying output stream.
125: */
126: protected OutputStream outputStream;
127:
128: /**
129: * Underlying output buffer.
130: */
131: protected OutputBuffer outputStreamOutputBuffer;
132:
133: /**
134: * Filter library.
135: * Note: Filter[0] is always the "chunked" filter.
136: */
137: protected OutputFilter[] filterLibrary;
138:
139: /**
140: * Active filter (which is actually the top of the pipeline).
141: */
142: protected OutputFilter[] activeFilters;
143:
144: /**
145: * Index of the last active filter.
146: */
147: protected int lastActiveFilter;
148:
149: /**
150: * Socket buffer.
151: */
152: protected ByteChunk socketBuffer;
153:
154: /**
155: * Socket buffer (extra buffering to reduce number of packets sent).
156: */
157: protected boolean useSocketBuffer = false;
158:
159: // ------------------------------------------------------------- Properties
160:
161: /**
162: * Set the underlying socket output stream.
163: */
164: public void setOutputStream(OutputStream outputStream) {
165:
166: // FIXME: Check for null ?
167:
168: this .outputStream = outputStream;
169:
170: }
171:
172: /**
173: * Get the underlying socket output stream.
174: */
175: public OutputStream getOutputStream() {
176:
177: return outputStream;
178:
179: }
180:
181: /**
182: * Set the socket buffer size.
183: */
184: public void setSocketBuffer(int socketBufferSize) {
185:
186: if (socketBufferSize > 500) {
187: useSocketBuffer = true;
188: socketBuffer.allocate(socketBufferSize, socketBufferSize);
189: } else {
190: useSocketBuffer = false;
191: }
192:
193: }
194:
195: /**
196: * Add an output filter to the filter library.
197: */
198: public void addFilter(OutputFilter filter) {
199:
200: OutputFilter[] newFilterLibrary = new OutputFilter[filterLibrary.length + 1];
201: for (int i = 0; i < filterLibrary.length; i++) {
202: newFilterLibrary[i] = filterLibrary[i];
203: }
204: newFilterLibrary[filterLibrary.length] = filter;
205: filterLibrary = newFilterLibrary;
206:
207: activeFilters = new OutputFilter[filterLibrary.length];
208:
209: }
210:
211: /**
212: * Get filters.
213: */
214: public OutputFilter[] getFilters() {
215:
216: return filterLibrary;
217:
218: }
219:
220: /**
221: * Clear filters.
222: */
223: public void clearFilters() {
224:
225: filterLibrary = new OutputFilter[0];
226: lastActiveFilter = -1;
227:
228: }
229:
230: /**
231: * Add an output filter to the filter library.
232: */
233: public void addActiveFilter(OutputFilter filter) {
234:
235: if (lastActiveFilter == -1) {
236: filter.setBuffer(outputStreamOutputBuffer);
237: } else {
238: for (int i = 0; i <= lastActiveFilter; i++) {
239: if (activeFilters[i] == filter)
240: return;
241: }
242: filter.setBuffer(activeFilters[lastActiveFilter]);
243: }
244:
245: activeFilters[++lastActiveFilter] = filter;
246:
247: filter.setResponse(response);
248:
249: }
250:
251: // --------------------------------------------------------- Public Methods
252:
253: /**
254: * Flush the response.
255: *
256: * @throws IOException an undelying I/O error occured
257: */
258: public void flush() throws IOException {
259:
260: if (!committed) {
261:
262: // Send the connector a request for commit. The connector should
263: // then validate the headers, send them (using sendHeader) and
264: // set the filters accordingly.
265: response.action(ActionCode.ACTION_COMMIT, null);
266:
267: }
268:
269: // Flush the current buffer
270: if (useSocketBuffer) {
271: socketBuffer.flushBuffer();
272: }
273:
274: }
275:
276: /**
277: * Reset current response.
278: *
279: * @throws IllegalStateException if the response has already been committed
280: */
281: public void reset() {
282:
283: if (committed)
284: throw new IllegalStateException(/*FIXME:Put an error message*/);
285:
286: // Recycle Request object
287: response.recycle();
288:
289: }
290:
291: /**
292: * Recycle the output buffer. This should be called when closing the
293: * connection.
294: */
295: public void recycle() {
296:
297: // Recycle Request object
298: response.recycle();
299: socketBuffer.recycle();
300:
301: outputStream = null;
302: buf = headerBuffer;
303: pos = 0;
304: lastActiveFilter = -1;
305: committed = false;
306: finished = false;
307:
308: }
309:
310: /**
311: * End processing of current HTTP request.
312: * Note: All bytes of the current request should have been already
313: * consumed. This method only resets all the pointers so that we are ready
314: * to parse the next HTTP request.
315: */
316: public void nextRequest() {
317:
318: // Recycle Request object
319: response.recycle();
320: socketBuffer.recycle();
321:
322: // Determine the header buffer used for next request
323: buf = headerBuffer;
324:
325: // Recycle filters
326: for (int i = 0; i <= lastActiveFilter; i++) {
327: activeFilters[i].recycle();
328: }
329:
330: // Reset pointers
331: pos = 0;
332: lastActiveFilter = -1;
333: committed = false;
334: finished = false;
335:
336: }
337:
338: /**
339: * End request.
340: *
341: * @throws IOException an undelying I/O error occured
342: */
343: public void endRequest() throws IOException {
344:
345: if (!committed) {
346:
347: // Send the connector a request for commit. The connector should
348: // then validate the headers, send them (using sendHeader) and
349: // set the filters accordingly.
350: response.action(ActionCode.ACTION_COMMIT, null);
351:
352: }
353:
354: if (finished)
355: return;
356:
357: if (lastActiveFilter != -1)
358: activeFilters[lastActiveFilter].end();
359:
360: if (useSocketBuffer) {
361: socketBuffer.flushBuffer();
362: }
363:
364: finished = true;
365:
366: }
367:
368: // ------------------------------------------------ HTTP/1.1 Output Methods
369:
370: /**
371: * Send an acknoledgement.
372: */
373: public void sendAck() throws IOException {
374:
375: if (!committed)
376: outputStream.write(Constants.ACK_BYTES);
377:
378: }
379:
380: /**
381: * Send the response status line.
382: */
383: public void sendStatus() {
384:
385: // Write protocol name
386: write("HTTP/1.1 ");
387:
388: // Write status code
389: int status = response.getStatus();
390: switch (status) {
391: case 200:
392: write("200");
393: break;
394: case 400:
395: write("400");
396: break;
397: case 404:
398: write("404");
399: break;
400: default:
401: write(status);
402: }
403:
404: write(" ");
405:
406: // Write message
407: String message = response.getMessage();
408: if (message == null) {
409: write(getMessage(status));
410: } else {
411: write(message);
412: }
413:
414: // End the response status line
415: if (System.getSecurityManager() != null) {
416: AccessController.doPrivileged(new PrivilegedAction() {
417: public Object run() {
418: write(Constants.CRLF_BYTES);
419: return null;
420: }
421: });
422: } else {
423: write(Constants.CRLF_BYTES);
424: }
425:
426: }
427:
428: private String getMessage(final int message) {
429: if (System.getSecurityManager() != null) {
430: return (String) AccessController
431: .doPrivileged(new PrivilegedAction() {
432: public Object run() {
433: return HttpMessages.getMessage(message);
434: }
435: });
436: } else {
437: return HttpMessages.getMessage(message);
438: }
439: }
440:
441: /**
442: * Send a header.
443: *
444: * @param name Header name
445: * @param value Header value
446: */
447: public void sendHeader(MessageBytes name, MessageBytes value) {
448:
449: write(name);
450: write(": ");
451: write(value);
452: write(Constants.CRLF_BYTES);
453:
454: }
455:
456: /**
457: * Send a header.
458: *
459: * @param name Header name
460: * @param value Header value
461: */
462: public void sendHeader(ByteChunk name, ByteChunk value) {
463:
464: write(name);
465: write(": ");
466: write(value);
467: write(Constants.CRLF_BYTES);
468:
469: }
470:
471: /**
472: * Send a header.
473: *
474: * @param name Header name
475: * @param value Header value
476: */
477: public void sendHeader(String name, String value) {
478:
479: write(name);
480: write(": ");
481: write(value);
482: write(Constants.CRLF_BYTES);
483:
484: }
485:
486: /**
487: * End the header block.
488: */
489: public void endHeaders() {
490:
491: write(Constants.CRLF_BYTES);
492:
493: }
494:
495: // --------------------------------------------------- OutputBuffer Methods
496:
497: /**
498: * Write the contents of a byte chunk.
499: *
500: * @param chunk byte chunk
501: * @return number of bytes written
502: * @throws IOException an undelying I/O error occured
503: */
504: public int doWrite(ByteChunk chunk, Response res)
505: throws IOException {
506:
507: if (!committed) {
508:
509: // Send the connector a request for commit. The connector should
510: // then validate the headers, send them (using sendHeaders) and
511: // set the filters accordingly.
512: response.action(ActionCode.ACTION_COMMIT, null);
513:
514: }
515:
516: if (lastActiveFilter == -1)
517: return outputStreamOutputBuffer.doWrite(chunk, res);
518: else
519: return activeFilters[lastActiveFilter].doWrite(chunk, res);
520:
521: }
522:
523: // ------------------------------------------------------ Protected Methods
524:
525: /**
526: * Commit the response.
527: *
528: * @throws IOException an undelying I/O error occured
529: */
530: protected void commit() throws IOException {
531:
532: // The response is now committed
533: committed = true;
534: response.setCommitted(true);
535:
536: if (pos > 0) {
537: // Sending the response header buffer
538: if (useSocketBuffer) {
539: socketBuffer.append(buf, 0, pos);
540: } else {
541: outputStream.write(buf, 0, pos);
542: }
543: }
544:
545: }
546:
547: /**
548: * This method will write the contents of the specyfied message bytes
549: * buffer to the output stream, without filtering. This method is meant to
550: * be used to write the response header.
551: *
552: * @param mb data to be written
553: */
554: protected void write(MessageBytes mb) {
555:
556: mb.toBytes();
557:
558: if (mb.getType() == MessageBytes.T_BYTES) {
559: ByteChunk bc = mb.getByteChunk();
560: write(bc);
561: } else {
562: write(mb.toString());
563: }
564:
565: }
566:
567: /**
568: * This method will write the contents of the specyfied message bytes
569: * buffer to the output stream, without filtering. This method is meant to
570: * be used to write the response header.
571: *
572: * @param bc data to be written
573: */
574: protected void write(ByteChunk bc) {
575:
576: // Writing the byte chunk to the output buffer
577: System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos, bc
578: .getLength());
579: pos = pos + bc.getLength();
580:
581: }
582:
583: /**
584: * This method will write the contents of the specyfied byte
585: * buffer to the output stream, without filtering. This method is meant to
586: * be used to write the response header.
587: *
588: * @param b data to be written
589: */
590: protected void write(byte[] b) {
591:
592: // Writing the byte chunk to the output buffer
593: System.arraycopy(b, 0, buf, pos, b.length);
594: pos = pos + b.length;
595:
596: }
597:
598: /**
599: * This method will write the contents of the specyfied String to the
600: * output stream, without filtering. This method is meant to be used to
601: * write the response header.
602: *
603: * @param s data to be written
604: */
605: protected void write(String s) {
606:
607: if (s == null)
608: return;
609:
610: // From the Tomcat 3.3 HTTP/1.0 connector
611: int len = s.length();
612: for (int i = 0; i < len; i++) {
613: char c = s.charAt(i);
614: // Note: This is clearly incorrect for many strings,
615: // but is the only consistent approach within the current
616: // servlet framework. It must suffice until servlet output
617: // streams properly encode their output.
618: if ((c & 0xff00) != 0) {
619: // High order byte must be zero
620: //log("Header character is not iso8859_1, " +
621: //"not supported yet: " + c, Log.ERROR ) ;
622: }
623: if (c != 9) {
624: if ((c >= 0) && (c <= 31)) {
625: c = ' ';
626: }
627: if (c == 127) {
628: c = ' ';
629: }
630: }
631: buf[pos++] = (byte) c;
632: }
633:
634: }
635:
636: /**
637: * This method will print the specified integer to the output stream,
638: * without filtering. This method is meant to be used to write the
639: * response header.
640: *
641: * @param i data to be written
642: */
643: protected void write(int i) {
644:
645: write(String.valueOf(i));
646:
647: }
648:
649: /**
650: * Callback to write data from the buffer.
651: */
652: public void realWriteBytes(byte cbuf[], int off, int len)
653: throws IOException {
654: if (len > 0) {
655: outputStream.write(cbuf, off, len);
656: }
657: }
658:
659: // ----------------------------------- OutputStreamOutputBuffer Inner Class
660:
661: /**
662: * This class is an output buffer which will write data to an output
663: * stream.
664: */
665: protected class OutputStreamOutputBuffer implements OutputBuffer {
666:
667: /**
668: * Write chunk.
669: */
670: public int doWrite(ByteChunk chunk, Response res)
671: throws IOException {
672:
673: if (useSocketBuffer) {
674: socketBuffer.append(chunk.getBuffer(),
675: chunk.getStart(), chunk.getLength());
676: } else {
677: outputStream.write(chunk.getBuffer(), chunk.getStart(),
678: chunk.getLength());
679: }
680: return chunk.getLength();
681:
682: }
683:
684: }
685:
686: }
|