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.util.Iterator;
020:
021: import org.mortbay.io.Buffer;
022: import org.mortbay.io.BufferUtil;
023: import org.mortbay.io.Buffers;
024: import org.mortbay.io.EndPoint;
025: import org.mortbay.io.Portable;
026: import org.mortbay.log.Log;
027:
028: /* ------------------------------------------------------------ */
029: /**
030: * HttpGenerator. Builds HTTP Messages.
031: *
032: * @author gregw
033: *
034: */
035: public class HttpGenerator extends AbstractGenerator {
036: // common _content
037: private static byte[] LAST_CHUNK = { (byte) '0', (byte) '\015',
038: (byte) '\012', (byte) '\015', (byte) '\012' };
039: private static byte[] CONTENT_LENGTH_0 = Portable
040: .getBytes("Content-Length: 0\015\012");
041: private static byte[] CONNECTION_KEEP_ALIVE = Portable
042: .getBytes("Connection: keep-alive\015\012");
043: private static byte[] CONNECTION_CLOSE = Portable
044: .getBytes("Connection: close\015\012");
045: private static byte[] TRANSFER_ENCODING_CHUNKED = Portable
046: .getBytes("Transfer-Encoding: chunked\015\012");
047: private static byte[] SERVER = Portable
048: .getBytes("Server: Jetty(6.0.x)\015\012");
049:
050: // other statics
051: private static int CHUNK_SPACE = 12;
052:
053: public static void setServerVersion(String version) {
054: SERVER = Portable.getBytes("Server: Jetty(" + version
055: + ")\015\012");
056: }
057:
058: // data
059: private boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
060: private boolean _needCRLF = false;
061: private boolean _needEOC = false;
062: private boolean _bufferChunked = false;
063:
064: /* ------------------------------------------------------------------------------- */
065: /**
066: * Constructor.
067: *
068: * @param buffers buffer pool
069: * @param headerBufferSize Size of the buffer to allocate for HTTP header
070: * @param contentBufferSize Size of the buffer to allocate for HTTP content
071: */
072: public HttpGenerator(Buffers buffers, EndPoint io,
073: int headerBufferSize, int contentBufferSize) {
074: super (buffers, io, headerBufferSize, contentBufferSize);
075: }
076:
077: /* ------------------------------------------------------------------------------- */
078: public void reset(boolean returnBuffers) {
079: super .reset(returnBuffers);
080: _bypass = false;
081: _needCRLF = false;
082: _needEOC = false;
083: _bufferChunked = false;
084: _method = null;
085: _uri = null;
086: _noContent = false;
087: }
088:
089: /* ------------------------------------------------------------ */
090: /**
091: * Add content.
092: *
093: * @param content
094: * @param last
095: * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
096: * @throws IllegalStateException If the request is not expecting any more content,
097: * or if the buffers are full and cannot be flushed.
098: * @throws IOException if there is a problem flushing the buffers.
099: */
100: public void addContent(Buffer content, boolean last)
101: throws IOException {
102: if (_noContent) {
103: content.clear();
104: return;
105: }
106:
107: if (_last || _state == STATE_END) {
108: Log.debug("Ignoring extra content {}", content);
109: content.clear();
110: return;
111: }
112: _last = last;
113:
114: // Handle any unfinished business?
115: if (_content != null && _content.length() > 0 || _bufferChunked) {
116: flush();
117: if (_content != null && _content.length() > 0
118: || _bufferChunked)
119: throw new IllegalStateException("FULL");
120: }
121:
122: _content = content;
123: _contentWritten += content.length();
124:
125: // Handle the _content
126: if (_head) {
127: content.clear();
128: _content = null;
129: } else if (_endp != null && _buffer == null
130: && content.length() > 0 && _last) {
131: // TODO - use bypass in more cases.
132: // Make _content a direct buffer
133: _bypass = true;
134: } else {
135: // Yes - so we better check we have a buffer
136: if (_buffer == null)
137: _buffer = _buffers.getBuffer(_contentBufferSize);
138:
139: // Copy _content to buffer;
140: int len = _buffer.put(_content);
141: _content.skip(len);
142: if (_content.length() == 0)
143: _content = null;
144: }
145: }
146:
147: /* ------------------------------------------------------------ */
148: /**
149: * Add content.
150: *
151: * @param b byte
152: * @return true if the buffers are full
153: * @throws IOException
154: */
155: public boolean addContent(byte b) throws IOException {
156: if (_noContent)
157: return false;
158:
159: if (_last || _state == STATE_END) {
160: Log.debug("Ignoring extra content {}", new Byte(b));
161: return false;
162: }
163:
164: // Handle any unfinished business?
165: if (_content != null && _content.length() > 0 || _bufferChunked) {
166: flush();
167: if (_content != null && _content.length() > 0
168: || _bufferChunked)
169: throw new IllegalStateException("FULL");
170: }
171:
172: _contentWritten++;
173:
174: // Handle the _content
175: if (_head)
176: return false;
177:
178: // we better check we have a buffer
179: if (_buffer == null)
180: _buffer = _buffers.getBuffer(_contentBufferSize);
181:
182: // Copy _content to buffer;
183: _buffer.put(b);
184:
185: return _buffer.space() <= (_contentLength == HttpTokens.CHUNKED_CONTENT ? CHUNK_SPACE
186: : 0);
187: }
188:
189: /* ------------------------------------------------------------ */
190: /** Prepare buffer for unchecked writes.
191: * Prepare the generator buffer to receive unchecked writes
192: * @return the available space in the buffer.
193: * @throws IOException
194: */
195: protected int prepareUncheckedAddContent() throws IOException {
196: if (_noContent)
197: return -1;
198:
199: if (_last || _state == STATE_END) {
200: Log.debug("Ignoring extra content {}");
201: return -1;
202: }
203:
204: // Handle any unfinished business?
205: Buffer content = _content;
206: if (content != null && content.length() > 0 || _bufferChunked) {
207: flush();
208: if (content != null && content.length() > 0
209: || _bufferChunked)
210: throw new IllegalStateException("FULL");
211: }
212:
213: // we better check we have a buffer
214: if (_buffer == null)
215: _buffer = _buffers.getBuffer(_contentBufferSize);
216:
217: _contentWritten -= _buffer.length();
218:
219: // Handle the _content
220: if (_head)
221: return Integer.MAX_VALUE;
222:
223: return _buffer.space()
224: - (_contentLength == HttpTokens.CHUNKED_CONTENT ? CHUNK_SPACE
225: : 0);
226: }
227:
228: /* ------------------------------------------------------------ */
229: public boolean isBufferFull() {
230: // Should we flush the buffers?
231: boolean full = super .isBufferFull()
232: || _bypass
233: || (_contentLength == HttpTokens.CHUNKED_CONTENT
234: && _buffer != null && _buffer.space() < CHUNK_SPACE);
235: return full;
236: }
237:
238: /* ------------------------------------------------------------ */
239: public void completeHeader(HttpFields fields,
240: boolean allContentAdded) throws IOException {
241: if (_state != STATE_HEADER)
242: return;
243:
244: if (_last && !allContentAdded)
245: throw new IllegalStateException("last?");
246: _last = _last | allContentAdded;
247:
248: // get a header buffer
249: if (_header == null)
250: _header = _buffers.getBuffer(_headerBufferSize);
251:
252: boolean has_server = false;
253:
254: if (_method != null) {
255: _close = false;
256: // Request
257: if (_version == HttpVersions.HTTP_0_9_ORDINAL) {
258: _contentLength = HttpTokens.NO_CONTENT;
259: _header.put(_method);
260: _header.put((byte) ' ');
261: _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
262: _header.put(HttpTokens.CRLF);
263: _state = STATE_FLUSHING;
264: _noContent = true;
265: return;
266: } else {
267: _header.put(_method);
268: _header.put((byte) ' ');
269: _header.put(_uri.getBytes("utf-8")); // TODO WRONG!
270: _header.put((byte) ' ');
271: _header
272: .put(_version == HttpVersions.HTTP_1_0_ORDINAL ? HttpVersions.HTTP_1_0_BUFFER
273: : HttpVersions.HTTP_1_1_BUFFER);
274: _header.put(HttpTokens.CRLF);
275: }
276: } else {
277: // Response
278: if (_version == HttpVersions.HTTP_0_9_ORDINAL) {
279: _close = true;
280: _contentLength = HttpTokens.EOF_CONTENT;
281: _state = STATE_CONTENT;
282: return;
283: } else {
284: if (_version == HttpVersions.HTTP_1_0_ORDINAL)
285: _close = true;
286:
287: // add response line
288: Buffer line = HttpStatus.getResponseLine(_status);
289:
290: if (line == null) {
291: if (_reason == null)
292: _reason = getReasonBuffer(_status);
293:
294: _header.put(HttpVersions.HTTP_1_1_BUFFER);
295: _header.put((byte) ' ');
296: _header.put((byte) ('0' + _status / 100));
297: _header.put((byte) ('0' + (_status % 100) / 10));
298: _header.put((byte) ('0' + (_status % 10)));
299: _header.put((byte) ' ');
300: if (_reason == null) {
301: _header.put((byte) ('0' + _status / 100));
302: _header
303: .put((byte) ('0' + (_status % 100) / 10));
304: _header.put((byte) ('0' + (_status % 10)));
305: } else
306: _header.put(_reason);
307: _header.put(HttpTokens.CRLF);
308: } else {
309: if (_reason == null)
310: _header.put(line);
311: else {
312: _header
313: .put(line.array(), 0,
314: HttpVersions.HTTP_1_1_BUFFER
315: .length() + 5);
316: _header.put(_reason);
317: _header.put(HttpTokens.CRLF);
318: }
319: }
320:
321: if (_status < 200 && _status >= 100) {
322: _noContent = true;
323: _content = null;
324: if (_buffer != null)
325: _buffer.clear();
326: // end the header.
327: _header.put(HttpTokens.CRLF);
328: _state = STATE_CONTENT;
329: return;
330: }
331:
332: if (_status == 204 || _status == 304) {
333: _noContent = true;
334: _content = null;
335: if (_buffer != null)
336: _buffer.clear();
337: }
338: }
339: }
340:
341: // Add headers
342:
343: // key field values
344: HttpFields.Field content_length = null;
345: HttpFields.Field transfer_encoding = null;
346: HttpFields.Field connection = null;
347: boolean keep_alive = false;
348:
349: if (fields != null) {
350: Iterator iter = fields.getFields();
351:
352: while (iter.hasNext()) {
353: HttpFields.Field field = (HttpFields.Field) iter.next();
354:
355: switch (field.getNameOrdinal()) {
356: case HttpHeaders.CONTENT_LENGTH_ORDINAL:
357: content_length = field;
358: _contentLength = field.getLongValue();
359:
360: if (_contentLength < _contentWritten || _last
361: && _contentLength != _contentWritten)
362: content_length = null;
363:
364: // write the field to the header buffer
365: field.put(_header);
366: break;
367:
368: case HttpHeaders.CONTENT_TYPE_ORDINAL:
369: if (BufferUtil.isPrefix(
370: MimeTypes.MULTIPART_BYTERANGES_BUFFER,
371: field.getValueBuffer()))
372: _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
373:
374: // write the field to the header buffer
375: field.put(_header);
376: break;
377:
378: case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
379: if (_version == HttpVersions.HTTP_1_1_ORDINAL)
380: transfer_encoding = field;
381: // Do NOT add yet!
382: break;
383:
384: case HttpHeaders.CONNECTION_ORDINAL:
385: connection = field;
386:
387: int connection_value = field.getValueOrdinal();
388:
389: // TODO handle multivalue HttpConnection
390: _close = _method == null
391: && HttpHeaderValues.CLOSE_ORDINAL == connection_value;
392: keep_alive = HttpHeaderValues.KEEP_ALIVE_ORDINAL == connection_value;
393: if (keep_alive
394: && _version == HttpVersions.HTTP_1_0_ORDINAL
395: && _method == null)
396: _close = false;
397:
398: if (_close
399: && _contentLength == HttpTokens.UNKNOWN_CONTENT)
400: _contentLength = HttpTokens.EOF_CONTENT;
401:
402: // Do NOT add yet!
403: break;
404:
405: case HttpHeaders.SERVER_ORDINAL:
406: if (getSendServerVersion()) {
407: has_server = true;
408: field.put(_header);
409: }
410: break;
411:
412: default:
413: // write the field to the header buffer
414: field.put(_header);
415: }
416: }
417: }
418:
419: // Calculate how to end _content and connection, _content length and transfer encoding
420: // settings.
421: // From RFC 2616 4.4:
422: // 1. No body for 1xx, 204, 304 & HEAD response
423: // 2. Force _content-length?
424: // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
425: // 4. Content-Length
426: // 5. multipart/byteranges
427: // 6. close
428: switch ((int) _contentLength) {
429: case HttpTokens.UNKNOWN_CONTENT:
430: // It may be that we have no _content, or perhaps _content just has not been
431: // written yet?
432:
433: // Response known not to have a body
434: if (_contentWritten == 0
435: && (_status < 200 || _status == 204 || _status == 304))
436: _contentLength = HttpTokens.NO_CONTENT;
437: else if (_last) {
438: // we have seen all the _content there is
439: _contentLength = _contentWritten;
440: if (content_length == null) {
441: // known length but not actually set.
442: _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
443: _header.put(HttpTokens.COLON);
444: _header.put((byte) ' ');
445: BufferUtil.putDecLong(_header, _contentLength);
446: _header.put(HttpTokens.CRLF);
447: }
448: } else {
449: // No idea, so we must assume that a body is coming
450: _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL) ? HttpTokens.EOF_CONTENT
451: : HttpTokens.CHUNKED_CONTENT;
452: if (_method != null
453: && _contentLength == HttpTokens.EOF_CONTENT)
454: throw new IllegalStateException("No Content-Length");
455: }
456: break;
457:
458: case HttpTokens.NO_CONTENT:
459: if (content_length == null && _status >= 200
460: && _status != 204 && _status != 304)
461: _header.put(CONTENT_LENGTH_0);
462: break;
463:
464: case HttpTokens.EOF_CONTENT:
465: _close = _method == null;
466: break;
467:
468: case HttpTokens.CHUNKED_CONTENT:
469: break;
470:
471: default:
472: // TODO - maybe allow forced chunking by setting te ???
473: break;
474: }
475:
476: // Add transfer_encoding if needed
477: if (_contentLength == HttpTokens.CHUNKED_CONTENT) {
478: // try to use user supplied encoding as it may have other values.
479: if (transfer_encoding != null
480: && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding
481: .getValueOrdinal()) {
482: String c = transfer_encoding.getValue();
483: if (c.endsWith(HttpHeaderValues.CHUNKED))
484: transfer_encoding.put(_header);
485: else
486: throw new IllegalArgumentException("BAD TE");
487: } else
488: _header.put(TRANSFER_ENCODING_CHUNKED);
489: }
490:
491: // Handle connection if need be
492: if ((_close || _contentLength == HttpTokens.EOF_CONTENT)) {
493: if (_version > HttpVersions.HTTP_1_0_ORDINAL
494: || connection != null)
495: _header.put(CONNECTION_CLOSE);
496: _close = true;
497: } else if (keep_alive
498: && _version == HttpVersions.HTTP_1_0_ORDINAL)
499: _header.put(CONNECTION_KEEP_ALIVE);
500: else if (connection != null)
501: connection.put(_header);
502:
503: if (!has_server && _status > 100 && getSendServerVersion())
504: _header.put(SERVER);
505:
506: // end the header.
507: _header.put(HttpTokens.CRLF);
508:
509: _state = STATE_CONTENT;
510:
511: }
512:
513: /* ------------------------------------------------------------ */
514: /**
515: * Complete the message.
516: *
517: * @throws IOException
518: */
519: public void complete() throws IOException {
520: if (_state == STATE_END)
521: return;
522:
523: super .complete();
524:
525: if (_state < STATE_FLUSHING) {
526: _state = STATE_FLUSHING;
527: if (_contentLength == HttpTokens.CHUNKED_CONTENT)
528: _needEOC = true;
529: }
530:
531: flush();
532: }
533:
534: /* ------------------------------------------------------------ */
535: public long flush() throws IOException {
536: try {
537: if (_state == STATE_HEADER)
538: throw new IllegalStateException("State==HEADER");
539:
540: prepareBuffers();
541:
542: if (_endp == null) {
543: if (_needCRLF && _buffer != null)
544: _buffer.put(HttpTokens.CRLF);
545: if (_needEOC && _buffer != null)
546: _buffer.put(LAST_CHUNK);
547: return 0;
548: }
549:
550: // Keep flushing while there is something to flush (except break below)
551: int total = 0;
552: long last_len = -1;
553: Flushing: while (true) {
554: int len = -1;
555: int to_flush = ((_header != null && _header.length() > 0) ? 4
556: : 0)
557: | ((_buffer != null && _buffer.length() > 0) ? 2
558: : 0)
559: | ((_bypass && _content != null && _content
560: .length() > 0) ? 1 : 0);
561: switch (to_flush) {
562: case 7:
563: throw new IllegalStateException(); // should never happen!
564: case 6:
565: len = _endp.flush(_header, _buffer, null);
566: break;
567: case 5:
568: len = _endp.flush(_header, _content, null);
569: break;
570: case 4:
571: len = _endp.flush(_header);
572: break;
573: case 3:
574: throw new IllegalStateException(); // should never happen!
575: case 2:
576: len = _endp.flush(_buffer);
577: break;
578: case 1:
579: len = _endp.flush(_content);
580: break;
581: case 0: {
582: // Nothing more we can write now.
583: if (_header != null)
584: _header.clear();
585:
586: _bypass = false;
587: _bufferChunked = false;
588:
589: if (_buffer != null) {
590: _buffer.clear();
591: if (_contentLength == HttpTokens.CHUNKED_CONTENT) {
592: // reserve some space for the chunk header
593: _buffer.setPutIndex(CHUNK_SPACE);
594: _buffer.setGetIndex(CHUNK_SPACE);
595:
596: // Special case handling for small left over buffer from
597: // an addContent that caused a buffer flush.
598: if (_content != null
599: && _content.length() < _buffer
600: .space()
601: && _state != STATE_FLUSHING) {
602: _buffer.put(_content);
603: _content.clear();
604: _content = null;
605: break Flushing;
606: }
607: }
608: }
609:
610: // Are we completely finished for now?
611: if (!_needCRLF
612: && !_needEOC
613: && (_content == null || _content.length() == 0)) {
614: if (_state == STATE_FLUSHING)
615: _state = STATE_END;
616: if (_state == STATE_END && _close)
617: _endp.close();
618:
619: break Flushing;
620: }
621:
622: // Try to prepare more to write.
623: prepareBuffers();
624: }
625: }
626:
627: // If we failed to flush anything twice in a row break
628: if (len <= 0) {
629: if (last_len <= 0)
630: break Flushing;
631: break;
632: }
633: last_len = len;
634: total += len;
635: }
636:
637: return total;
638: } catch (IOException e) {
639: Log.ignore(e);
640: throw (e instanceof EofException) ? e : new EofException(e);
641: }
642: }
643:
644: /* ------------------------------------------------------------ */
645: private void prepareBuffers() {
646: // if we are not flushing an existing chunk
647: if (!_bufferChunked) {
648: // Refill buffer if possible
649: if (_content != null && _content.length() > 0
650: && _buffer != null && _buffer.space() > 0) {
651: int len = _buffer.put(_content);
652: _content.skip(len);
653: if (_content.length() == 0)
654: _content = null;
655: }
656:
657: // Chunk buffer if need be
658: if (_contentLength == HttpTokens.CHUNKED_CONTENT) {
659: int size = _buffer == null ? 0 : _buffer.length();
660: if (size > 0) {
661: // Prepare a chunk!
662: _bufferChunked = true;
663:
664: // Did we leave space at the start of the buffer.
665: if (_buffer.getIndex() == CHUNK_SPACE) {
666: // Oh yes, goodie! let's use it then!
667: _buffer.poke(_buffer.getIndex() - 2,
668: HttpTokens.CRLF, 0, 2);
669: _buffer.setGetIndex(_buffer.getIndex() - 2);
670: BufferUtil.prependHexInt(_buffer, size);
671:
672: if (_needCRLF) {
673: _buffer.poke(_buffer.getIndex() - 2,
674: HttpTokens.CRLF, 0, 2);
675: _buffer.setGetIndex(_buffer.getIndex() - 2);
676: _needCRLF = false;
677: }
678: } else {
679: // No space so lets use the header buffer.
680: if (_needCRLF) {
681: if (_header.length() > 0)
682: throw new IllegalStateException("EOC");
683: _header.put(HttpTokens.CRLF);
684: _needCRLF = false;
685: }
686: BufferUtil.putHexInt(_header, size);
687: _header.put(HttpTokens.CRLF);
688: }
689:
690: // Add end chunk trailer.
691: if (_buffer.space() >= 2)
692: _buffer.put(HttpTokens.CRLF);
693: else
694: _needCRLF = true;
695: }
696:
697: // If we need EOC and everything written
698: if (_needEOC
699: && (_content == null || _content.length() == 0)) {
700: if (_needCRLF) {
701: if (_buffer == null && _header.space() >= 2) {
702: _header.put(HttpTokens.CRLF);
703: _needCRLF = false;
704: } else if (_buffer != null
705: && _buffer.space() >= 2) {
706: _buffer.put(HttpTokens.CRLF);
707: _needCRLF = false;
708: }
709: }
710:
711: if (!_needCRLF && _needEOC) {
712: if (_buffer == null
713: && _header.space() >= LAST_CHUNK.length) {
714: _header.put(LAST_CHUNK);
715: _bufferChunked = true;
716: _needEOC = false;
717: } else if (_buffer != null
718: && _buffer.space() >= LAST_CHUNK.length) {
719: _buffer.put(LAST_CHUNK);
720: _bufferChunked = true;
721: _needEOC = false;
722: }
723: }
724: }
725: }
726: }
727:
728: if (_content != null && _content.length() == 0)
729: _content = null;
730:
731: }
732:
733: }
|