org.eclipse.jetty.http.HttpGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
// // ======================================================================== // Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.http; import java.io.IOException; import java.io.InterruptedIOException; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.BufferCache.CachedBuffer; import org.eclipse.jetty.io.BufferUtil; import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** * HttpGenerator. Builds HTTP Messages. * * * */ public class HttpGenerator extends AbstractGenerator { private static final Logger LOG = Log.getLogger(HttpGenerator.class); // Build cache of response lines for status private static class Status { Buffer _reason; Buffer _schemeCode; Buffer _responseLine; } private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1]; static { int versionLength=HttpVersions.HTTP_1_1_BUFFER.length(); for (int i=0;i<__status.length;i++) { HttpStatus.Code code = HttpStatus.getCode(i); if (code==null) continue; String reason=code.getMessage(); byte[] bytes=new byte[versionLength+5+reason.length()+2]; HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength); bytes[versionLength+0]=' '; bytes[versionLength+1]=(byte)('0'+i/100); bytes[versionLength+2]=(byte)('0'+(i%100)/10); bytes[versionLength+3]=(byte)('0'+(i%10)); bytes[versionLength+4]=' '; for (int j=0;j
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) throw new IllegalStateException("NO CONTENT"); if (_last || _state==STATE_END) { LOG.warn("Ignoring extra content {}",content); content.clear(); return; } _last = last; // Handle any unfinished business? if (_content!=null && _content.length()>0 || _bufferChunked) { if (_endp.isOutputShutdown()) throw new EofException(); flushBuffer(); if (_content != null && _content.length()>0) { if (_bufferChunked) { Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length()); nc.put(_content); nc.put(HttpTokens.CRLF); BufferUtil.putHexInt(nc, content.length()); nc.put(HttpTokens.CRLF); nc.put(content); content=nc; } else { Buffer nc=_buffers.getBuffer(_content.length()+content.length()); nc.put(_content); nc.put(content); content=nc; } } } _content = content; _contentWritten += content.length(); // Handle the _content if (_head) { content.clear(); _content=null; } else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024)) { _bypass = true; } else if (!_bufferChunked) { // Yes - so we better check we have a buffer if (_buffer == null) _buffer = _buffers.getBuffer(); // Copy _content to buffer; int len=_buffer.put(_content); _content.skip(len); if (_content.length() == 0) _content = null; } } /* ------------------------------------------------------------ */ /** * send complete response. * * @param response */ public void sendResponse(Buffer response) throws IOException { if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head ) throw new IllegalStateException(); _last = true; _content = response; _bypass = true; _state = STATE_FLUSHING; // TODO this is not exactly right, but should do. _contentLength =_contentWritten = response.length(); } /* ------------------------------------------------------------ */ /** Prepare buffer for unchecked writes. * Prepare the generator buffer to receive unchecked writes * @return the available space in the buffer. * @throws IOException */ @Override public 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) { flushBuffer(); if (content != null && content.length()>0 || _bufferChunked) throw new IllegalStateException("FULL"); } // we better check we have a buffer if (_buffer == null) _buffer = _buffers.getBuffer(); _contentWritten-=_buffer.length(); // Handle the _content if (_head) return Integer.MAX_VALUE; return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0); } /* ------------------------------------------------------------ */ @Override public boolean isBufferFull() { // Should we flush the buffers? return super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE); } /* ------------------------------------------------------------ */ public void send1xx(int code) throws IOException { if (_state != STATE_HEADER) return; if (code<100||code>199) throw new IllegalArgumentException("!1xx"); Status status=__status[code]; if (status==null) throw new IllegalArgumentException(code+"?"); // get a header buffer if (_header == null) _header = _buffers.getHeader(); _header.put(status._responseLine); _header.put(HttpTokens.CRLF); try { // nasty semi busy flush! while(_header.length()>0) { int len = _endp.flush(_header); if (len<0) throw new EofException(); if (len==0) Thread.sleep(100); } } catch(InterruptedException e) { LOG.debug(e); throw new InterruptedIOException(e.toString()); } } /* ------------------------------------------------------------ */ @Override public boolean isRequest() { return _method!=null; } /* ------------------------------------------------------------ */ @Override public boolean isResponse() { return _method==null; } /* ------------------------------------------------------------ */ @Override public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException { if (_state != STATE_HEADER) return; // handle a reset if (isResponse() && _status==0) throw new EofException(); if (_last && !allContentAdded) throw new IllegalStateException("last?"); _last = _last | allContentAdded; // get a header buffer if (_header == null) _header = _buffers.getHeader(); boolean has_server = false; try { if (isRequest()) { _persistent=true; if (_version == HttpVersions.HTTP_0_9_ORDINAL) { _contentLength = HttpTokens.NO_CONTENT; _header.put(_method); _header.put((byte)' '); _header.put(_uri.getBytes("UTF-8")); // TODO check _header.put(HttpTokens.CRLF); _state = STATE_FLUSHING; _noContent=true; return; } else { _header.put(_method); _header.put((byte)' '); _header.put(_uri.getBytes("UTF-8")); // TODO check _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 { // Responses if (_version == HttpVersions.HTTP_0_9_ORDINAL) { _persistent = false; _contentLength = HttpTokens.EOF_CONTENT; _state = STATE_CONTENT; return; } else { if (_persistent==null) _persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL); // add response line Status status = _status<__status.length?__status[_status]:null; if (status==null) { _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(status._responseLine); else { _header.put(status._schemeCode); _header.put(_reason); _header.put(HttpTokens.CRLF); } } if (_status<200 && _status>=100 ) { _noContent=true; _content=null; if (_buffer!=null) _buffer.clear(); // end the header. if (_status!=101 ) { _header.put(HttpTokens.CRLF); _state = STATE_CONTENT; return; } } else if (_status==204 || _status==304) { _noContent=true; _content=null; if (_buffer!=null) _buffer.clear(); } } } // Add headers if (_status>=200 && _date!=null) { _header.put(HttpHeaders.DATE_BUFFER); _header.put((byte)':'); _header.put((byte)' '); _header.put(_date); _header.put(CRLF); } // key field values HttpFields.Field content_length = null; HttpFields.Field transfer_encoding = null; boolean keep_alive = false; boolean close=false; boolean content_type=false; StringBuilder connection = null; if (fields != null) { int s=fields.size(); for (int f=0;fcontent 0 || content_type ) && !_noContent) { // 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 = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT; if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT) { _contentLength=HttpTokens.NO_CONTENT; _noContent=true; } } break; case HttpTokens.NO_CONTENT: if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304) _header.put(CONTENT_LENGTH_0); break; case HttpTokens.EOF_CONTENT: _persistent = isRequest(); 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.putTo(_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; _persistent=false; } if (isResponse()) { if (!_persistent && (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>199 && getSendServerVersion()) _header.put(SERVER); // end the header. _header.put(HttpTokens.CRLF); _state = STATE_CONTENT; } catch(ArrayIndexOutOfBoundsException e) { throw new RuntimeException("Header>"+_header.capacity(),e); } } /* ------------------------------------------------------------ */ /** * Complete the message. * * @throws IOException */ @Override 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; } flushBuffer(); } /* ------------------------------------------------------------ */ @Override public int flushBuffer() 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; } int total= 0; int len = -1; int to_flush = flushMask(); int last_flush; do { last_flush=to_flush; 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: len = _endp.flush(_buffer, _content, null); break; case 2: len = _endp.flush(_buffer); break; case 1: len = _endp.flush(_content); break; case 0: { len=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; } } } // 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 && _persistent != null && !_persistent && _status!=100 && _method==null) _endp.shutdownOutput(); } else // Try to prepare more to write. prepareBuffers(); } } if (len > 0) total+=len; to_flush = flushMask(); } // loop while progress is being made (OR we have prepared some buffers that might make progress) while (len>0 || (to_flush!=0 && last_flush==0)); return total; } catch (IOException e) { LOG.ignore(e); throw (e instanceof EofException) ? e:new EofException(e); } } /* ------------------------------------------------------------ */ private int flushMask() { return ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0); } /* ------------------------------------------------------------ */ private void prepareBuffers() { // if we are not flushing an existing chunk if (!_bufferChunked) { // Refill buffer if possible if (!_bypass && _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) { if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null) { // this is a bypass write int size = _content.length(); _bufferChunked = true; if (_header == null) _header = _buffers.getHeader(); // if we need CRLF add this to header if (_needCRLF) { if (_header.length() > 0) throw new IllegalStateException("EOC"); _header.put(HttpTokens.CRLF); _needCRLF = false; } // Add the chunk size to the header BufferUtil.putHexInt(_header, size); _header.put(HttpTokens.CRLF); // Need a CRLF after the content _needCRLF=true; } else if (_buffer!=null) { int size = _buffer.length(); if (size > 0) { // Prepare a chunk! _bufferChunked = true; // Did we leave space at the start of the buffer. //noinspection ConstantConditions 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 a header buffer. if (_header == null) _header = _buffers.getHeader(); 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; } public int getBytesBuffered() { return(_header==null?0:_header.length())+ (_buffer==null?0:_buffer.length())+ (_content==null?0:_content.length()); } public boolean isEmpty() { return (_header==null||_header.length()==0) && (_buffer==null||_buffer.length()==0) && (_content==null||_content.length()==0); } @Override public String toString() { return String.format("%s{s=%d,h=%d,b=%d,c=%d}", getClass().getSimpleName(), _state, _header == null ? -1 : _header.length(), _buffer == null ? -1 : _buffer.length(), _content == null ? -1 : _content.length()); } }