001: //========================================================================
002: //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $
003: //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
004: //------------------------------------------------------------------------
005: //Licensed under the Apache License, Version 2.0 (the "License");
006: //you may not use this file except in compliance with the License.
007: //You may obtain a copy of the License at
008: //http://www.apache.org/licenses/LICENSE-2.0
009: //Unless required by applicable law or agreed to in writing, software
010: //distributed under the License is distributed on an "AS IS" BASIS,
011: //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: //See the License for the specific language governing permissions and
013: //limitations under the License.
014: //========================================================================
015:
016: package org.mortbay.jetty;
017:
018: import java.io.IOException;
019: import java.io.OutputStreamWriter;
020: import java.io.Writer;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.Modifier;
023:
024: import javax.servlet.ServletOutputStream;
025: import javax.servlet.http.HttpServletResponse;
026:
027: import org.mortbay.io.Buffer;
028: import org.mortbay.io.Buffers;
029: import org.mortbay.io.ByteArrayBuffer;
030: import org.mortbay.io.EndPoint;
031: import org.mortbay.io.View;
032: import org.mortbay.log.Log;
033: import org.mortbay.util.ByteArrayOutputStream2;
034: import org.mortbay.util.StringUtil;
035: import org.mortbay.util.TypeUtil;
036:
037: /* ------------------------------------------------------------ */
038: /**
039: * Abstract Generator. Builds HTTP Messages.
040: *
041: * Currently this class uses a system parameter "jetty.direct.writers" to control
042: * two optional writer to byte conversions. buffer.writers=true will probably be
043: * faster, but will consume more memory. This option is just for testing and tuning.
044: *
045: * @author gregw
046: *
047: */
048: public abstract class AbstractGenerator implements Generator {
049: // states
050: public final static int STATE_HEADER = 0;
051: public final static int STATE_CONTENT = 2;
052: public final static int STATE_FLUSHING = 3;
053: public final static int STATE_END = 4;
054:
055: private static byte[] NO_BYTES = {};
056: private static int MAX_OUTPUT_CHARS = 1024;
057:
058: private static Buffer[] __reasons = new Buffer[505];
059: static {
060: Field[] fields = HttpServletResponse.class.getDeclaredFields();
061: for (int i = 0; i < fields.length; i++) {
062: if ((fields[i].getModifiers() & Modifier.STATIC) != 0
063: && fields[i].getName().startsWith("SC_")) {
064: try {
065: int code = fields[i].getInt(null);
066: if (code < __reasons.length)
067: __reasons[code] = new ByteArrayBuffer(fields[i]
068: .getName().substring(3));
069: } catch (IllegalAccessException e) {
070: }
071: }
072: }
073: }
074:
075: protected static Buffer getReasonBuffer(int code) {
076: Buffer reason = (code < __reasons.length) ? __reasons[code]
077: : null;
078: return reason == null ? null : reason;
079: }
080:
081: public static String getReason(int code) {
082: Buffer reason = (code < __reasons.length) ? __reasons[code]
083: : null;
084: return reason == null ? TypeUtil.toString(code) : reason
085: .toString();
086: }
087:
088: // data
089: protected int _state = STATE_HEADER;
090:
091: protected int _status = 0;
092: protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
093: protected Buffer _reason;
094: protected Buffer _method;
095: protected String _uri;
096:
097: protected long _contentWritten = 0;
098: protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
099: protected boolean _last = false;
100: protected boolean _head = false;
101: protected boolean _noContent = false;
102: protected boolean _close = false;
103:
104: protected Buffers _buffers; // source of buffers
105: protected EndPoint _endp;
106:
107: protected int _headerBufferSize;
108: protected int _contentBufferSize;
109:
110: protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
111: protected Buffer _buffer; // Buffer for copy of passed _content
112: protected Buffer _content; // Buffer passed to addContent
113:
114: private boolean _sendServerVersion;
115:
116: /* ------------------------------------------------------------------------------- */
117: /**
118: * Constructor.
119: *
120: * @param buffers buffer pool
121: * @param headerBufferSize Size of the buffer to allocate for HTTP header
122: * @param contentBufferSize Size of the buffer to allocate for HTTP content
123: */
124: public AbstractGenerator(Buffers buffers, EndPoint io,
125: int headerBufferSize, int contentBufferSize) {
126: this ._buffers = buffers;
127: this ._endp = io;
128: _headerBufferSize = headerBufferSize;
129: _contentBufferSize = contentBufferSize;
130: }
131:
132: /* ------------------------------------------------------------------------------- */
133: public void reset(boolean returnBuffers) {
134: _state = STATE_HEADER;
135: _status = 0;
136: _version = HttpVersions.HTTP_1_1_ORDINAL;
137: _last = false;
138: _head = false;
139: _noContent = false;
140: _close = false;
141: _contentWritten = 0;
142: _contentLength = HttpTokens.UNKNOWN_CONTENT;
143:
144: if (returnBuffers) {
145: if (_header != null)
146: _buffers.returnBuffer(_header);
147: _header = null;
148: if (_buffer != null)
149: _buffers.returnBuffer(_buffer);
150: _buffer = null;
151: } else {
152: if (_header != null)
153: _header.clear();
154:
155: if (_buffer != null) {
156: _buffers.returnBuffer(_buffer);
157: _buffer = null;
158: }
159: }
160: _content = null;
161: _method = null;
162: }
163:
164: /* ------------------------------------------------------------------------------- */
165: public void resetBuffer() {
166: if (_state >= STATE_FLUSHING)
167: throw new IllegalStateException("Flushed");
168:
169: _last = false;
170: _close = false;
171: _contentWritten = 0;
172: _contentLength = HttpTokens.UNKNOWN_CONTENT;
173: _content = null;
174: if (_buffer != null)
175: _buffer.clear();
176: }
177:
178: /* ------------------------------------------------------------ */
179: /**
180: * @return Returns the contentBufferSize.
181: */
182: public int getContentBufferSize() {
183: return _contentBufferSize;
184: }
185:
186: /* ------------------------------------------------------------ */
187: /**
188: * @param contentBufferSize The contentBufferSize to set.
189: */
190: public void increaseContentBufferSize(int contentBufferSize) {
191: if (contentBufferSize > _contentBufferSize) {
192: _contentBufferSize = contentBufferSize;
193: if (_buffer != null) {
194: Buffer nb = _buffers.getBuffer(_contentBufferSize);
195: nb.put(_buffer);
196: _buffers.returnBuffer(_buffer);
197: _buffer = nb;
198: }
199: }
200: }
201:
202: /* ------------------------------------------------------------ */
203: public Buffer getUncheckedBuffer() {
204: return _buffer;
205: }
206:
207: /* ------------------------------------------------------------ */
208: public boolean getSendServerVersion() {
209: return _sendServerVersion;
210: }
211:
212: /* ------------------------------------------------------------ */
213: public void setSendServerVersion(boolean sendServerVersion) {
214: _sendServerVersion = sendServerVersion;
215: }
216:
217: /* ------------------------------------------------------------ */
218: public int getState() {
219: return _state;
220: }
221:
222: /* ------------------------------------------------------------ */
223: public boolean isState(int state) {
224: return _state == state;
225: }
226:
227: /* ------------------------------------------------------------ */
228: public boolean isComplete() {
229: return _state == STATE_END;
230: }
231:
232: /* ------------------------------------------------------------ */
233: public boolean isIdle() {
234: return _state == STATE_HEADER && _method == null
235: && _status == 0;
236: }
237:
238: /* ------------------------------------------------------------ */
239: public boolean isCommitted() {
240: return _state != STATE_HEADER;
241: }
242:
243: /* ------------------------------------------------------------ */
244: /**
245: * @return Returns the head.
246: */
247: public boolean isHead() {
248: return _head;
249: }
250:
251: /* ------------------------------------------------------------ */
252: public void setContentLength(long value) {
253: _contentLength = value;
254: }
255:
256: /* ------------------------------------------------------------ */
257: /**
258: * @param head The head to set.
259: */
260: public void setHead(boolean head) {
261: _head = head;
262: }
263:
264: /* ------------------------------------------------------------ */
265: /**
266: * @return <code>false</code> if the connection should be closed after a request has been read,
267: * <code>true</code> if it should be used for additional requests.
268: */
269: public boolean isPersistent() {
270: return !_close;
271: }
272:
273: /* ------------------------------------------------------------ */
274: public void setPersistent(boolean persistent) {
275: _close = !persistent;
276: }
277:
278: /* ------------------------------------------------------------ */
279: /**
280: * @param version The version of the client the response is being sent to (NB. Not the version
281: * in the response, which is the version of the server).
282: */
283: public void setVersion(int version) {
284: if (_state != STATE_HEADER)
285: throw new IllegalStateException("STATE!=START");
286: _version = version;
287: if (_version == HttpVersions.HTTP_0_9_ORDINAL
288: && _method != null)
289: _noContent = true;
290: }
291:
292: /* ------------------------------------------------------------ */
293: public int getVersion() {
294: return _version;
295: }
296:
297: /* ------------------------------------------------------------ */
298: /**
299: */
300: public void setRequest(String method, String uri) {
301: if (method == null || HttpMethods.GET.equals(method))
302: _method = HttpMethods.GET_BUFFER;
303: else
304: _method = HttpMethods.CACHE.lookup(method);
305: _uri = uri;
306: if (_version == HttpVersions.HTTP_0_9_ORDINAL)
307: _noContent = true;
308: }
309:
310: /* ------------------------------------------------------------ */
311: /**
312: * @param status The status code to send.
313: * @param reason the status message to send.
314: */
315: public void setResponse(int status, String reason) {
316: if (_state != STATE_HEADER)
317: throw new IllegalStateException("STATE!=START");
318:
319: _status = status;
320: if (reason != null) {
321: int len = reason.length();
322: if (len > _headerBufferSize / 2)
323: len = _headerBufferSize / 2;
324: _reason = new ByteArrayBuffer(len);
325: for (int i = 0; i < len; i++) {
326: char ch = reason.charAt(i);
327: if (Character.isWhitespace(ch))
328: _reason.put((byte) '_');
329: else if (Character.isJavaIdentifierPart(ch))
330: _reason.put((byte) ch);
331: }
332: }
333: }
334:
335: /* ------------------------------------------------------------ */
336: /** Prepare buffer for unchecked writes.
337: * Prepare the generator buffer to receive unchecked writes
338: * @return the available space in the buffer.
339: * @throws IOException
340: */
341: protected abstract int prepareUncheckedAddContent()
342: throws IOException;
343:
344: /* ------------------------------------------------------------ */
345: void uncheckedAddContent(int b) {
346: _buffer.put((byte) b);
347: }
348:
349: /* ------------------------------------------------------------ */
350: void completeUncheckedAddContent() {
351: if (_noContent) {
352: if (_buffer != null)
353: _buffer.clear();
354: return;
355: } else {
356: _contentWritten += _buffer.length();
357: if (_head)
358: _buffer.clear();
359: }
360: }
361:
362: /* ------------------------------------------------------------ */
363: public boolean isBufferFull() {
364: // Should we flush the buffers?
365: boolean full = (_state == STATE_FLUSHING || (_buffer != null && _buffer
366: .space() == 0));
367: return full;
368: }
369:
370: /* ------------------------------------------------------------ */
371: public boolean isContentWritten() {
372: return _contentLength >= 0 && _contentWritten >= _contentLength;
373: }
374:
375: /* ------------------------------------------------------------ */
376: public abstract void completeHeader(HttpFields fields,
377: boolean allContentAdded) throws IOException;
378:
379: /* ------------------------------------------------------------ */
380: /**
381: * Complete the message.
382: *
383: * @throws IOException
384: */
385: public void complete() throws IOException {
386: if (_state == STATE_HEADER) {
387: throw new IllegalStateException("State==HEADER");
388: }
389:
390: if (_contentLength >= 0 && _contentLength != _contentWritten
391: && !_head) {
392: if (Log.isDebugEnabled())
393: Log.debug("ContentLength written==" + _contentWritten
394: + " != contentLength==" + _contentLength);
395: _close = true;
396: }
397: }
398:
399: /* ------------------------------------------------------------ */
400: public abstract long flush() throws IOException;
401:
402: /* ------------------------------------------------------------ */
403: /**
404: * Utility method to send an error response. If the builder is not committed, this call is
405: * equivalent to a setResponse, addcontent and complete call.
406: *
407: * @param code
408: * @param reason
409: * @param content
410: * @param close
411: * @throws IOException
412: */
413: public void sendError(int code, String reason, String content,
414: boolean close) throws IOException {
415: if (!isCommitted()) {
416: setResponse(code, reason);
417: _close = close;
418: completeHeader(null, false);
419: if (content != null)
420: addContent(new View(new ByteArrayBuffer(content)),
421: Generator.LAST);
422: complete();
423: }
424: }
425:
426: /* ------------------------------------------------------------ */
427: /**
428: * @return Returns the contentWritten.
429: */
430: public long getContentWritten() {
431: return _contentWritten;
432: }
433:
434: /* ------------------------------------------------------------ */
435: /* ------------------------------------------------------------ */
436: /* ------------------------------------------------------------ */
437: /* ------------------------------------------------------------ */
438: /** Output.
439: *
440: * <p>
441: * Implements {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.
442: * </p>
443: * A {@link ServletOutputStream} implementation that writes content
444: * to a {@link AbstractGenerator}. The class is designed to be reused
445: * and can be reopened after a close.
446: */
447: public static class Output extends ServletOutputStream {
448: protected AbstractGenerator _generator;
449: protected long _maxIdleTime;
450: protected ByteArrayBuffer _buf = new ByteArrayBuffer(NO_BYTES);
451: protected boolean _closed;
452:
453: // These are held here for reuse by Writer
454: String _characterEncoding;
455: Writer _converter;
456: char[] _chars;
457: ByteArrayOutputStream2 _bytes;
458:
459: /* ------------------------------------------------------------ */
460: public Output(AbstractGenerator generator, long maxIdleTime) {
461: _generator = generator;
462: _maxIdleTime = maxIdleTime;
463: }
464:
465: /* ------------------------------------------------------------ */
466: /*
467: * @see java.io.OutputStream#close()
468: */
469: public void close() throws IOException {
470: _closed = true;
471: }
472:
473: /* ------------------------------------------------------------ */
474: void blockForOutput() throws IOException {
475: if (_generator._endp.isBlocking()) {
476: try {
477: flush();
478: } catch (IOException e) {
479: _generator._endp.close();
480: throw e;
481: }
482: } else {
483: if (!_generator._endp.blockWritable(_maxIdleTime)) {
484: _generator._endp.close();
485: throw new EofException("timeout");
486: }
487:
488: _generator.flush();
489: }
490: }
491:
492: /* ------------------------------------------------------------ */
493: void reopen() {
494: _closed = false;
495: }
496:
497: /* ------------------------------------------------------------ */
498: public void flush() throws IOException {
499: // block until everything is flushed
500: Buffer content = _generator._content;
501: Buffer buffer = _generator._buffer;
502: if (content != null && content.length() > 0
503: || buffer != null && buffer.length() > 0) {
504: _generator.flush();
505:
506: while ((content != null && content.length() > 0 || buffer != null
507: && buffer.length() > 0)
508: && _generator._endp.isOpen())
509: blockForOutput();
510: }
511: }
512:
513: /* ------------------------------------------------------------ */
514: public void write(byte[] b, int off, int len)
515: throws IOException {
516: _buf.wrap(b, off, len);
517: write(_buf);
518: }
519:
520: /* ------------------------------------------------------------ */
521: /*
522: * @see java.io.OutputStream#write(byte[])
523: */
524: public void write(byte[] b) throws IOException {
525: _buf.wrap(b);
526: write(_buf);
527: }
528:
529: /* ------------------------------------------------------------ */
530: /*
531: * @see java.io.OutputStream#write(int)
532: */
533: public void write(int b) throws IOException {
534: if (_closed)
535: throw new IOException("Closed");
536:
537: // Block until we can add _content.
538: while (_generator.isBufferFull()
539: && _generator._endp.isOpen())
540: blockForOutput();
541:
542: // Add the _content
543: if (_generator.addContent((byte) b)
544: || _generator.isContentWritten()) {
545: // Buffers are full so flush.
546: flush();
547: }
548: }
549:
550: /* ------------------------------------------------------------ */
551: private void write(Buffer buffer) throws IOException {
552: if (_closed)
553: throw new IOException("Closed");
554:
555: // Block until we can add _content.
556: while (_generator.isBufferFull()
557: && _generator._endp.isOpen())
558: blockForOutput();
559:
560: // Add the _content
561: _generator.addContent(buffer, Generator.MORE);
562:
563: // Have to flush and complete headers?
564: if (_generator.isBufferFull()
565: || _generator.isContentWritten())
566: flush();
567:
568: // Block until our buffer is free
569: while (buffer.length() > 0 && _generator._endp.isOpen())
570: blockForOutput();
571: }
572:
573: /* ------------------------------------------------------------ */
574: /*
575: * @see javax.servlet.ServletOutputStream#print(java.lang.String)
576: */
577: public void print(String s) throws IOException {
578: write(s.getBytes());
579: }
580: }
581:
582: /* ------------------------------------------------------------ */
583: /* ------------------------------------------------------------ */
584: /* ------------------------------------------------------------ */
585: /** OutputWriter.
586: * A writer that can wrap a {@link Output} stream and provide
587: * character encodings.
588: *
589: * The UTF-8 encoding is done by this class and no additional
590: * buffers or Writers are used.
591: * The UTF-8 code was inspired by http://javolution.org
592: */
593: public static class OutputWriter extends Writer {
594: private static final int WRITE_CONV = 0;
595: private static final int WRITE_ISO1 = 1;
596: private static final int WRITE_UTF8 = 2;
597:
598: Output _out;
599: AbstractGenerator _generator;
600: int _writeMode;
601: int _surrogate;
602:
603: /* ------------------------------------------------------------ */
604: public OutputWriter(Output out) {
605: _out = out;
606: _generator = _out._generator;
607:
608: }
609:
610: /* ------------------------------------------------------------ */
611: public void setCharacterEncoding(String encoding) {
612: if (encoding == null
613: || StringUtil.__ISO_8859_1
614: .equalsIgnoreCase(encoding)) {
615: _writeMode = WRITE_ISO1;
616: } else if (StringUtil.__UTF8.equalsIgnoreCase(encoding)) {
617: _writeMode = WRITE_UTF8;
618: } else {
619: _writeMode = WRITE_CONV;
620: if (_out._characterEncoding == null
621: || !_out._characterEncoding
622: .equalsIgnoreCase(encoding))
623: _out._converter = null; // Set lazily in getConverter()
624: }
625:
626: _out._characterEncoding = encoding;
627: if (_out._bytes == null)
628: _out._bytes = new ByteArrayOutputStream2(
629: MAX_OUTPUT_CHARS * 6);
630: }
631:
632: /* ------------------------------------------------------------ */
633: public void close() throws IOException {
634: _out.close();
635: }
636:
637: /* ------------------------------------------------------------ */
638: public void flush() throws IOException {
639: _out.flush();
640: }
641:
642: /* ------------------------------------------------------------ */
643: public void write(String s, int offset, int length)
644: throws IOException {
645: while (length > MAX_OUTPUT_CHARS) {
646: write(s, offset, MAX_OUTPUT_CHARS);
647: offset += MAX_OUTPUT_CHARS;
648: length -= MAX_OUTPUT_CHARS;
649: }
650:
651: if (_out._chars == null)
652: _out._chars = new char[MAX_OUTPUT_CHARS];
653: char[] chars = _out._chars;
654: s.getChars(offset, offset + length, chars, 0);
655: write(chars, 0, length);
656: }
657:
658: /* ------------------------------------------------------------ */
659: public void write(char[] s, int offset, int length)
660: throws IOException {
661: while (length > MAX_OUTPUT_CHARS) {
662: write(s, offset, MAX_OUTPUT_CHARS);
663: offset += MAX_OUTPUT_CHARS;
664: length -= MAX_OUTPUT_CHARS;
665: }
666:
667: Output out = _out;
668:
669: switch (_writeMode) {
670: case WRITE_CONV: {
671: Writer converter = getConverter();
672: converter.write(s, offset, length);
673: converter.flush();
674: }
675: break;
676:
677: case WRITE_ISO1: {
678:
679: byte[] buffer = out._bytes.getBuf();
680: int count = out._bytes.getCount();
681:
682: for (int i = 0; i < length; i++) {
683: int c = s[offset + i];
684: buffer[count++] = (byte) ((c >= 0 && c < 256) ? c
685: : '?'); // ISO-1 and UTF-8 match for 0 - 255
686: }
687: if (count >= 0)
688: out._bytes.setCount(count);
689:
690: break;
691: }
692:
693: case WRITE_UTF8: {
694: byte[] buffer = out._bytes.getBuf();
695: int count = out._bytes.getCount();
696: for (int i = 0; i < length; i++) {
697: int code = s[offset + i];
698:
699: if ((code & 0xffffff80) == 0) {
700: // 1b
701: buffer[count++] = (byte) (code);
702: } else if ((code & 0xfffff800) == 0) {
703: // 2b
704: buffer[count++] = (byte) (0xc0 | (code >> 6));
705: buffer[count++] = (byte) (0x80 | (code & 0x3f));
706: } else if ((code & 0xffff0000) == 0) {
707: // 3b
708: buffer[count++] = (byte) (0xe0 | (code >> 12));
709: buffer[count++] = (byte) (0x80 | ((code >> 6) & 0x3f));
710: buffer[count++] = (byte) (0x80 | (code & 0x3f));
711: } else if ((code & 0xff200000) == 0) {
712: // 4b
713: buffer[count++] = (byte) (0xf0 | (code >> 18));
714: buffer[count++] = (byte) (0x80 | ((code >> 12) & 0x3f));
715: buffer[count++] = (byte) (0x80 | ((code >> 6) & 0x3f));
716: buffer[count++] = (byte) (0x80 | (code & 0x3f));
717: } else if ((code & 0xf4000000) == 0) {
718: // 5
719: buffer[count++] = (byte) (0xf8 | (code >> 24));
720: buffer[count++] = (byte) (0x80 | ((code >> 18) & 0x3f));
721: buffer[count++] = (byte) (0x80 | ((code >> 12) & 0x3f));
722: buffer[count++] = (byte) (0x80 | ((code >> 6) & 0x3f));
723: buffer[count++] = (byte) (0x80 | (code & 0x3f));
724: } else if ((code & 0x80000000) == 0) {
725: // 6b
726: buffer[count++] = (byte) (0xfc | (code >> 30));
727: buffer[count++] = (byte) (0x80 | ((code >> 24) & 0x3f));
728: buffer[count++] = (byte) (0x80 | ((code >> 18) & 0x3f));
729: buffer[count++] = (byte) (0x80 | ((code >> 12) & 0x3f));
730: buffer[count++] = (byte) (0x80 | ((code >> 6) & 0x3f));
731: buffer[count++] = (byte) (0x80 | (code & 0x3f));
732: } else {
733: buffer[count++] = (byte) ('?');
734: }
735: }
736: out._bytes.setCount(count);
737: break;
738: }
739: default:
740: throw new IllegalStateException();
741: }
742: out._bytes.writeTo(out);
743: out._bytes.reset();
744: }
745:
746: /* ------------------------------------------------------------ */
747: private Writer getConverter() throws IOException {
748: if (_out._converter == null)
749: _out._converter = new OutputStreamWriter(_out._bytes,
750: _out._characterEncoding);
751: return _out._converter;
752: }
753: }
754: }
|