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