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