|
Jetty example source code file (HttpGenerator.java)
The Jetty HttpGenerator.java source code//======================================================================== //$Id: HttpGenerator.java,v 1.7 2005/11/25 21:17:12 gregwilkins Exp $ //Copyright 2004-2005 Mort Bay Consulting Pty. Ltd. //------------------------------------------------------------------------ //Licensed under the Apache License, Version 2.0 (the "License"); //you may not use this file except in compliance with the License. //You may obtain a copy of the License at //http://www.apache.org/licenses/LICENSE-2.0 //Unless required by applicable law or agreed to in writing, software //distributed under the License is distributed on an "AS IS" BASIS, //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //See the License for the specific language governing permissions and //limitations under the License. //======================================================================== package org.mortbay.jetty; import java.io.IOException; import java.util.Iterator; import org.mortbay.io.Buffer; import org.mortbay.io.BufferUtil; import org.mortbay.io.Buffers; import org.mortbay.io.EndPoint; import org.mortbay.io.Portable; import org.mortbay.io.BufferCache.CachedBuffer; import org.mortbay.log.Log; /* ------------------------------------------------------------ */ /** * HttpGenerator. Builds HTTP Messages. * * @author gregw * */ public class HttpGenerator extends AbstractGenerator { // common _content private static byte[] LAST_CHUNK = { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'}; private static byte[] CONTENT_LENGTH_0 = Portable.getBytes("Content-Length: 0\015\012"); private static byte[] CONNECTION_KEEP_ALIVE = Portable.getBytes("Connection: keep-alive\015\012"); private static byte[] CONNECTION_CLOSE = Portable.getBytes("Connection: close\015\012"); private static byte[] CONNECTION_ = Portable.getBytes("Connection: "); private static byte[] CRLF = Portable.getBytes("\015\012"); private static byte[] TRANSFER_ENCODING_CHUNKED = Portable.getBytes("Transfer-Encoding: chunked\015\012"); private static byte[] SERVER = Portable.getBytes("Server: Jetty(6.0.x)\015\012"); // other statics private static int CHUNK_SPACE = 12; public static void setServerVersion(String version) { SERVER=Portable.getBytes("Server: Jetty("+version+")\015\012"); } // data private boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer private boolean _needCRLF = false; private boolean _needEOC = false; private boolean _bufferChunked = false; /* ------------------------------------------------------------------------------- */ /** * Constructor. * * @param buffers buffer pool * @param headerBufferSize Size of the buffer to allocate for HTTP header * @param contentBufferSize Size of the buffer to allocate for HTTP content */ public HttpGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize) { super(buffers,io,headerBufferSize,contentBufferSize); } /* ------------------------------------------------------------------------------- */ public void reset(boolean returnBuffers) { super.reset(returnBuffers); _bypass = false; _needCRLF = false; _needEOC = false; _bufferChunked=false; _method=null; _uri=null; _noContent=false; } /* ------------------------------------------------------------ */ /** * Add content. * * @param content * @param last * @throws IllegalArgumentException if <code>content is {@link Buffer#isImmutable immutable}. * @throws IllegalStateException If the request is not expecting any more content, * or if the buffers are full and cannot be flushed. * @throws IOException if there is a problem flushing the buffers. */ public void addContent(Buffer content, boolean last) throws IOException { if (_noContent) { content.clear(); return; } if (_last || _state==STATE_END) { Log.debug("Ignoring extra content {}",content); content.clear(); return; } _last = last; // Handle any unfinished business? if (_content!=null && _content.length()>0 || _bufferChunked) { if (!_endp.isOpen()) throw new EofException(); flush(); if (_content != null && _content.length()>0 || _bufferChunked) throw new IllegalStateException("FULL"); } _content = content; _contentWritten += content.length(); // Handle the _content if (_head) { content.clear(); _content=null; } else if (_endp != null && _buffer == null && content.length() > 0 && _last) { // TODO - use bypass in more cases. // Make _content a direct buffer _bypass = true; } else { // Yes - so we better check we have a buffer if (_buffer == null) _buffer = _buffers.getBuffer(_contentBufferSize); // Copy _content to buffer; int len=_buffer.put(_content); _content.skip(len); if (_content.length() == 0) _content = null; } } /* ------------------------------------------------------------ */ /** * Add content. * * @param b byte * @return true if the buffers are full * @throws IOException */ public boolean addContent(byte b) throws IOException { if (_noContent) return false; if (_last || _state==STATE_END) { Log.debug("Ignoring extra content {}",new Byte(b)); return false; } // Handle any unfinished business? if (_content != null && _content.length()>0 || _bufferChunked) { flush(); if (_content != null && _content.length()>0 || _bufferChunked) throw new IllegalStateException("FULL"); } _contentWritten++; // Handle the _content if (_head) return false; // we better check we have a buffer if (_buffer == null) _buffer = _buffers.getBuffer(_contentBufferSize); // Copy _content to buffer; _buffer.put(b); return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); } /* ------------------------------------------------------------ */ /** Prepare buffer for unchecked writes. * Prepare the generator buffer to receive unchecked writes * @return the available space in the buffer. * @throws IOException */ protected int prepareUncheckedAddContent() throws IOException { if (_noContent) return -1; if (_last || _state==STATE_END) return -1; // Handle any unfinished business? Buffer content = _content; if (content != null && content.length()>0 || _bufferChunked) { flush(); if (content != null && content.length()>0 || _bufferChunked) throw new IllegalStateException("FULL"); } // we better check we have a buffer if (_buffer == null) _buffer = _buffers.getBuffer(_contentBufferSize); _contentWritten-=_buffer.length(); // Handle the _content if (_head) return Integer.MAX_VALUE; return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); } /* ------------------------------------------------------------ */ public boolean isBufferFull() { // Should we flush the buffers? boolean full = super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE); return full; } /* ------------------------------------------------------------ */ public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException { if (_state != STATE_HEADER) return; // handle a reset if (_method==null && _status==0) throw new EofException(); if (_last && !allContentAdded) throw new IllegalStateException("last?"); _last = _last | allContentAdded; // get a header buffer if (_header == null) _header = _buffers.getBuffer(_headerBufferSize); boolean has_server = false; if (_method!=null) { _close = false; // Request if (_version == HttpVersions.HTTP_0_9_ORDINAL) { _contentLength = HttpTokens.NO_CONTENT; _header.put(_method); _header.put((byte)' '); _header.put(_uri.getBytes("utf-8")); // TODO WRONG! _header.put(HttpTokens.CRLF); _state = STATE_FLUSHING; _noContent=true; return; } else { _header.put(_method); _header.put((byte)' '); _header.put(_uri.getBytes("utf-8")); // TODO WRONG! _header.put((byte)' '); _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER); _header.put(HttpTokens.CRLF); } } else { // Response if (_version == HttpVersions.HTTP_0_9_ORDINAL) { _close = true; _contentLength = HttpTokens.EOF_CONTENT; _state = STATE_CONTENT; return; } else { if (_version == HttpVersions.HTTP_1_0_ORDINAL) _close = true; // add response line Buffer line = HttpStatus.getResponseLine(_status); if (line==null) { if (_reason==null) _reason=getReasonBuffer(_status); _header.put(HttpVersions.HTTP_1_1_BUFFER); _header.put((byte) ' '); _header.put((byte) ('0' + _status / 100)); _header.put((byte) ('0' + (_status % 100) / 10)); _header.put((byte) ('0' + (_status % 10))); _header.put((byte) ' '); if (_reason==null) { _header.put((byte) ('0' + _status / 100)); _header.put((byte) ('0' + (_status % 100) / 10)); _header.put((byte) ('0' + (_status % 10))); } else _header.put(_reason); _header.put(HttpTokens.CRLF); } else { if (_reason==null) _header.put(line); else { _header.put(line.array(), 0, HttpVersions.HTTP_1_1_BUFFER.length() + 5); _header.put(_reason); _header.put(HttpTokens.CRLF); } } if (_status<200 && _status>=100 ) { _noContent=true; _content=null; if (_buffer!=null) _buffer.clear(); // end the header. _header.put(HttpTokens.CRLF); _state = STATE_CONTENT; return; } if (_status==204 || _status==304) { _noContent=true; _content=null; if (_buffer!=null) _buffer.clear(); } } } // Add headers // key field values HttpFields.Field content_length = null; HttpFields.Field transfer_encoding = null; boolean keep_alive = false; boolean close=false; StringBuffer connection = null; if (fields != null) { Iterator iter = fields.getFields(); while (iter.hasNext()) { HttpFields.Field field = (HttpFields.Field) iter.next(); switch (field.getNameOrdinal()) { case HttpHeaders.CONTENT_LENGTH_ORDINAL: content_length = field; _contentLength = field.getLongValue(); if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten) content_length = null; // write the field to the header buffer field.put(_header); break; case HttpHeaders.CONTENT_TYPE_ORDINAL: if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT; // write the field to the header buffer field.put(_header); break; case HttpHeaders.TRANSFER_ENCODING_ORDINAL: if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field; // Do NOT add yet! break; case HttpHeaders.CONNECTION_ORDINAL: int connection_value = field.getValueOrdinal(); switch (connection_value) { case -1: { String[] values = field.getValue().split(","); for (int i=0;values!=null && i<values.length;i++) { CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim()); if (cb!=null) { switch(cb.getOrdinal()) { case HttpHeaderValues.CLOSE_ORDINAL: close=true; if (_method==null) _close=true; keep_alive=false; if (_close && _contentLength == HttpTokens.UNKNOWN_CONTENT) _contentLength = HttpTokens.EOF_CONTENT; break; case HttpHeaderValues.KEEP_ALIVE_ORDINAL: if (_version == HttpVersions.HTTP_1_0_ORDINAL) { keep_alive = true; if (_method==null) _close = false; } break; default: if (connection==null) connection=new StringBuffer(); else connection.append(','); connection.append(values[i]); } } else { if (connection==null) connection=new StringBuffer(); else connection.append(','); connection.append(values[i]); } } break; } case HttpHeaderValues.CLOSE_ORDINAL: { close=true; if (_method==null) _close=true; if (_close && _contentLength == HttpTokens.UNKNOWN_CONTENT) _contentLength = HttpTokens.EOF_CONTENT; break; } case HttpHeaderValues.KEEP_ALIVE_ORDINAL: { if (_version == HttpVersions.HTTP_1_0_ORDINAL) { keep_alive = true; if (_method==null) _close = false; } break; } default: { if (connection==null) connection=new StringBuffer(); else connection.append(','); connection.append(field.getValue()); } } // Do NOT add yet! break; case HttpHeaders.SERVER_ORDINAL: if (getSendServerVersion()) { has_server=true; field.put(_header); } break; default: // write the field to the header buffer field.put(_header); } } } // Calculate how to end _content and connection, _content length and transfer encoding // settings. // From RFC 2616 4.4: // 1. No body for 1xx, 204, 304 & HEAD response // 2. Force _content-length? // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk // 4. Content-Length // 5. multipart/byteranges // 6. close switch ((int) _contentLength) { case HttpTokens.UNKNOWN_CONTENT: // It may be that we have no _content, or perhaps _content just has not been // written yet? // Response known not to have a body if (_contentWritten == 0 && (_status < 200 || _status == 204 || _status == 304)) _contentLength = HttpTokens.NO_CONTENT; else if (_last) { // we have seen all the _content there is _contentLength = _contentWritten; if (content_length == null) { // known length but not actually set. _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER); _header.put(HttpTokens.COLON); _header.put((byte) ' '); BufferUtil.putDecLong(_header, _contentLength); _header.put(HttpTokens.CRLF); } } else { // No idea, so we must assume that a body is coming _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT; if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT) throw new IllegalStateException("No Content-Length"); } break; case HttpTokens.NO_CONTENT: if (content_length == null && _status >= 200 && _status != 204 && _status != 304) _header.put(CONTENT_LENGTH_0); break; case HttpTokens.EOF_CONTENT: _close = _method==null; break; case HttpTokens.CHUNKED_CONTENT: break; default: // TODO - maybe allow forced chunking by setting te ??? break; } // Add transfer_encoding if needed if (_contentLength == HttpTokens.CHUNKED_CONTENT) { // try to use user supplied encoding as it may have other values. if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal()) { String c = transfer_encoding.getValue(); if (c.endsWith(HttpHeaderValues.CHUNKED)) transfer_encoding.put(_header); else throw new IllegalArgumentException("BAD TE"); } else _header.put(TRANSFER_ENCODING_CHUNKED); } // Handle connection if need be if (_contentLength==HttpTokens.EOF_CONTENT) { keep_alive=false; _close=true; } if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL)) { _header.put(CONNECTION_CLOSE); if (connection!=null) { _header.setPutIndex(_header.putIndex()-2); _header.put((byte)','); _header.put(connection.toString().getBytes()); _header.put(CRLF); } } else if (keep_alive) { _header.put(CONNECTION_KEEP_ALIVE); if (connection!=null) { _header.setPutIndex(_header.putIndex()-2); _header.put((byte)','); _header.put(connection.toString().getBytes()); _header.put(CRLF); } } else if (connection!=null) { _header.put(CONNECTION_); _header.put(connection.toString().getBytes()); _header.put(CRLF); } if (!has_server && _status>100 && getSendServerVersion()) _header.put(SERVER); // end the header. _header.put(HttpTokens.CRLF); _state = STATE_CONTENT; } /* ------------------------------------------------------------ */ /** * Complete the message. * * @throws IOException */ public void complete() throws IOException { if (_state == STATE_END) return; super.complete(); if (_state < STATE_FLUSHING) { _state = STATE_FLUSHING; if (_contentLength == HttpTokens.CHUNKED_CONTENT) _needEOC = true; } flush(); } /* ------------------------------------------------------------ */ public long flush() throws IOException { try { if (_state == STATE_HEADER) throw new IllegalStateException("State==HEADER"); prepareBuffers(); if (_endp == null) { if (_needCRLF && _buffer!=null) _buffer.put(HttpTokens.CRLF); if (_needEOC && _buffer!=null && !_head) _buffer.put(LAST_CHUNK); _needCRLF=false; _needEOC=false; return 0; } // Keep flushing while there is something to flush (except break below) int total= 0; long last_len = -1; Flushing: while (true) { int len = -1; int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0); switch (to_flush) { case 7: throw new IllegalStateException(); // should never happen! case 6: len = _endp.flush(_header, _buffer, null); break; case 5: len = _endp.flush(_header, _content, null); break; case 4: len = _endp.flush(_header); break; case 3: throw new IllegalStateException(); // should never happen! case 2: len = _endp.flush(_buffer); break; case 1: len = _endp.flush(_content); break; case 0: { // Nothing more we can write now. if (_header != null) _header.clear(); _bypass = false; _bufferChunked = false; if (_buffer != null) { _buffer.clear(); if (_contentLength == HttpTokens.CHUNKED_CONTENT) { // reserve some space for the chunk header _buffer.setPutIndex(CHUNK_SPACE); _buffer.setGetIndex(CHUNK_SPACE); // Special case handling for small left over buffer from // an addContent that caused a buffer flush. if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING) { _buffer.put(_content); _content.clear(); _content = null; break Flushing; } } } // Are we completely finished for now? if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0)) { if (_state == STATE_FLUSHING) _state = STATE_END; if (_state==STATE_END && _close && _status!=100) _endp.close(); break Flushing; } // Try to prepare more to write. prepareBuffers(); } } // If we failed to flush anything twice in a row break if (len <= 0) { if (last_len <= 0) break Flushing; break; } last_len = len; total+=len; } return total; } catch (IOException e) { Log.ignore(e); throw (e instanceof EofException) ? e:new EofException(e); } } /* ------------------------------------------------------------ */ private void prepareBuffers() { // if we are not flushing an existing chunk if (!_bufferChunked) { // Refill buffer if possible if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0) { int len = _buffer.put(_content); _content.skip(len); if (_content.length() == 0) _content = null; } // Chunk buffer if need be if (_contentLength == HttpTokens.CHUNKED_CONTENT) { int size = _buffer == null ? 0 : _buffer.length(); if (size > 0) { // Prepare a chunk! _bufferChunked = true; // Did we leave space at the start of the buffer. if (_buffer.getIndex() == CHUNK_SPACE) { // Oh yes, goodie! let's use it then! _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); _buffer.setGetIndex(_buffer.getIndex() - 2); BufferUtil.prependHexInt(_buffer, size); if (_needCRLF) { _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2); _buffer.setGetIndex(_buffer.getIndex() - 2); _needCRLF = false; } } else { // No space so lets use the header buffer. if (_needCRLF) { if (_header.length() > 0) throw new IllegalStateException("EOC"); _header.put(HttpTokens.CRLF); _needCRLF = false; } BufferUtil.putHexInt(_header, size); _header.put(HttpTokens.CRLF); } // Add end chunk trailer. if (_buffer.space() >= 2) _buffer.put(HttpTokens.CRLF); else _needCRLF = true; } // If we need EOC and everything written if (_needEOC && (_content == null || _content.length() == 0)) { if (_needCRLF) { if (_buffer == null && _header.space() >= 2) { _header.put(HttpTokens.CRLF); _needCRLF = false; } else if (_buffer!=null && _buffer.space() >= 2) { _buffer.put(HttpTokens.CRLF); _needCRLF = false; } } if (!_needCRLF && _needEOC) { if (_buffer == null && _header.space() >= LAST_CHUNK.length) { if (!_head) { _header.put(LAST_CHUNK); _bufferChunked=true; } _needEOC = false; } else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length) { if (!_head) { _buffer.put(LAST_CHUNK); _bufferChunked=true; } _needEOC = false; } } } } } if (_content != null && _content.length() == 0) _content = null; } } Other Jetty examples (source code examples)Here is a short list of links related to this Jetty HttpGenerator.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.