
org.mortbay.jetty.AbstractGenerator Maven / Gradle / Ivy
//========================================================================
//$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.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.mortbay.io.Buffer;
import org.mortbay.io.Buffers;
import org.mortbay.io.ByteArrayBuffer;
import org.mortbay.io.EndPoint;
import org.mortbay.io.View;
import org.mortbay.log.Log;
import org.mortbay.util.ByteArrayOutputStream2;
import org.mortbay.util.StringUtil;
import org.mortbay.util.TypeUtil;
/* ------------------------------------------------------------ */
/**
* Abstract Generator. Builds HTTP Messages.
*
* Currently this class uses a system parameter "jetty.direct.writers" to control
* two optional writer to byte conversions. buffer.writers=true will probably be
* faster, but will consume more memory. This option is just for testing and tuning.
*
* @author gregw
*
*/
public abstract class AbstractGenerator implements Generator
{
// states
public final static int STATE_HEADER = 0;
public final static int STATE_CONTENT = 2;
public final static int STATE_FLUSHING = 3;
public final static int STATE_END = 4;
private static byte[] NO_BYTES = {};
private static int MAX_OUTPUT_CHARS = 512;
private static Buffer[] __reasons = new Buffer[505];
static
{
Field[] fields = HttpServletResponse.class.getDeclaredFields();
for (int i=0;i=STATE_FLUSHING)
throw new IllegalStateException("Flushed");
_last = false;
_close = false;
_contentWritten = 0;
_contentLength = HttpTokens.UNKNOWN_CONTENT;
_content=null;
if (_buffer!=null)
_buffer.clear();
}
/* ------------------------------------------------------------ */
/**
* @return Returns the contentBufferSize.
*/
public int getContentBufferSize()
{
return _contentBufferSize;
}
/* ------------------------------------------------------------ */
/**
* @param contentBufferSize The contentBufferSize to set.
*/
public void increaseContentBufferSize(int contentBufferSize)
{
if (contentBufferSize > _contentBufferSize)
{
_contentBufferSize = contentBufferSize;
if (_buffer != null)
{
Buffer nb = _buffers.getBuffer(_contentBufferSize);
nb.put(_buffer);
_buffers.returnBuffer(_buffer);
_buffer = nb;
}
}
}
/* ------------------------------------------------------------ */
public Buffer getUncheckedBuffer()
{
return _buffer;
}
/* ------------------------------------------------------------ */
public boolean getSendServerVersion ()
{
return _sendServerVersion;
}
/* ------------------------------------------------------------ */
public void setSendServerVersion (boolean sendServerVersion)
{
_sendServerVersion = sendServerVersion;
}
/* ------------------------------------------------------------ */
public int getState()
{
return _state;
}
/* ------------------------------------------------------------ */
public boolean isState(int state)
{
return _state == state;
}
/* ------------------------------------------------------------ */
public boolean isComplete()
{
return _state == STATE_END;
}
/* ------------------------------------------------------------ */
public boolean isIdle()
{
return _state == STATE_HEADER && _method==null && _status==0;
}
/* ------------------------------------------------------------ */
public boolean isCommitted()
{
return _state != STATE_HEADER;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the head.
*/
public boolean isHead()
{
return _head;
}
/* ------------------------------------------------------------ */
public void setContentLength(long value)
{
if (value<0)
_contentLength=HttpTokens.UNKNOWN_CONTENT;
else
_contentLength=value;
}
/* ------------------------------------------------------------ */
/**
* @param head The head to set.
*/
public void setHead(boolean head)
{
_head = head;
}
/* ------------------------------------------------------------ */
/**
* @return false
if the connection should be closed after a request has been read,
* true
if it should be used for additional requests.
*/
public boolean isPersistent()
{
return !_close;
}
/* ------------------------------------------------------------ */
public void setPersistent(boolean persistent)
{
_close=!persistent;
}
/* ------------------------------------------------------------ */
/**
* @param version The version of the client the response is being sent to (NB. Not the version
* in the response, which is the version of the server).
*/
public void setVersion(int version)
{
if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
_version = version;
if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
_noContent=true;
}
/* ------------------------------------------------------------ */
public int getVersion()
{
return _version;
}
/* ------------------------------------------------------------ */
/**
*/
public void setRequest(String method, String uri)
{
if (method==null || HttpMethods.GET.equals(method) )
_method=HttpMethods.GET_BUFFER;
else
_method=HttpMethods.CACHE.lookup(method);
_uri=uri;
if (_version==HttpVersions.HTTP_0_9_ORDINAL)
_noContent=true;
}
/* ------------------------------------------------------------ */
/**
* @param status The status code to send.
* @param reason the status message to send.
*/
public void setResponse(int status, String reason)
{
if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
_status = status;
if (reason!=null)
{
int len=reason.length();
if (len>_headerBufferSize/2)
len=_headerBufferSize/2;
_reason=new ByteArrayBuffer(len);
for (int i=0;i0;
}
/* ------------------------------------------------------------ */
public boolean isContentWritten()
{
return _contentLength>=0 && _contentWritten>=_contentLength;
}
/* ------------------------------------------------------------ */
public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
/* ------------------------------------------------------------ */
/**
* Complete the message.
*
* @throws IOException
*/
public void complete() throws IOException
{
if (_state == STATE_HEADER)
{
throw new IllegalStateException("State==HEADER");
}
if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
{
if (Log.isDebugEnabled())
Log.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
_close = true;
}
}
/* ------------------------------------------------------------ */
public abstract long flush() throws IOException;
/* ------------------------------------------------------------ */
/**
* Utility method to send an error response. If the builder is not committed, this call is
* equivalent to a setResponse, addcontent and complete call.
*
* @param code
* @param reason
* @param content
* @param close
* @throws IOException
*/
public void sendError(int code, String reason, String content, boolean close) throws IOException
{
if (!isCommitted())
{
setResponse(code, reason);
_close = close;
completeHeader(null, false);
if (content != null)
addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
complete();
}
}
/* ------------------------------------------------------------ */
/**
* @return Returns the contentWritten.
*/
public long getContentWritten()
{
return _contentWritten;
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/** Output.
*
*
* Implements {@link javax.servlet.ServletOutputStream} from the {@link javax.servlet} package.
*
* A {@link ServletOutputStream} implementation that writes content
* to a {@link AbstractGenerator}. The class is designed to be reused
* and can be reopened after a close.
*/
public static class Output extends ServletOutputStream
{
protected AbstractGenerator _generator;
protected long _maxIdleTime;
protected ByteArrayBuffer _buf = new ByteArrayBuffer(NO_BYTES);
protected boolean _closed;
// These are held here for reuse by Writer
String _characterEncoding;
Writer _converter;
char[] _chars;
ByteArrayOutputStream2 _bytes;
/* ------------------------------------------------------------ */
public Output(AbstractGenerator generator, long maxIdleTime)
{
_generator=generator;
_maxIdleTime=maxIdleTime;
}
/* ------------------------------------------------------------ */
/*
* @see java.io.OutputStream#close()
*/
public void close() throws IOException
{
_closed=true;
}
/* ------------------------------------------------------------ */
void blockForOutput() throws IOException
{
if (_generator._endp.isBlocking())
{
try
{
flush();
}
catch(IOException e)
{
_generator._endp.close();
throw e;
}
}
else
{
if (!_generator._endp.blockWritable(_maxIdleTime))
{
_generator._endp.close();
throw new EofException("timeout");
}
_generator.flush();
}
}
/* ------------------------------------------------------------ */
void reopen()
{
_closed=false;
}
/* ------------------------------------------------------------ */
public void flush() throws IOException
{
// block until everything is flushed
Buffer content = _generator._content;
Buffer buffer = _generator._buffer;
if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || _generator.isBufferFull())
{
_generator.flush();
while ((content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _generator._endp.isOpen())
blockForOutput();
}
}
/* ------------------------------------------------------------ */
public void write(byte[] b, int off, int len) throws IOException
{
_buf.wrap(b, off, len);
write(_buf);
}
/* ------------------------------------------------------------ */
/*
* @see java.io.OutputStream#write(byte[])
*/
public void write(byte[] b) throws IOException
{
_buf.wrap(b);
write(_buf);
}
/* ------------------------------------------------------------ */
/*
* @see java.io.OutputStream#write(int)
*/
public void write(int b) throws IOException
{
if (_closed)
throw new IOException("Closed");
if (!_generator._endp.isOpen())
throw new EofException();
// Block until we can add _content.
while (_generator.isBufferFull())
{
blockForOutput();
if (_closed)
throw new IOException("Closed");
if (!_generator._endp.isOpen())
throw new EofException();
}
// Add the _content
if (_generator.addContent((byte)b))
// Buffers are full so flush.
flush();
if (_generator.isContentWritten())
{
flush();
close();
}
}
/* ------------------------------------------------------------ */
private void write(Buffer buffer) throws IOException
{
if (_closed)
throw new IOException("Closed");
if (!_generator._endp.isOpen())
throw new EofException();
// Block until we can add _content.
while (_generator.isBufferFull())
{
blockForOutput();
if (_closed)
throw new IOException("Closed");
if (!_generator._endp.isOpen())
throw new EofException();
}
// Add the _content
_generator.addContent(buffer, Generator.MORE);
// Have to flush and complete headers?
if (_generator.isBufferFull())
flush();
if (_generator.isContentWritten())
{
flush();
close();
}
// Block until our buffer is free
while (buffer.length() > 0 && _generator._endp.isOpen())
blockForOutput();
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.ServletOutputStream#print(java.lang.String)
*/
public void print(String s) throws IOException
{
write(s.getBytes());
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/** OutputWriter.
* A writer that can wrap a {@link Output} stream and provide
* character encodings.
*
* The UTF-8 encoding is done by this class and no additional
* buffers or Writers are used.
* The UTF-8 code was inspired by http://javolution.org
*/
public static class OutputWriter extends Writer
{
private static final int WRITE_CONV = 0;
private static final int WRITE_ISO1 = 1;
private static final int WRITE_UTF8 = 2;
Output _out;
AbstractGenerator _generator;
int _writeMode;
int _surrogate;
/* ------------------------------------------------------------ */
public OutputWriter(Output out)
{
_out=out;
_generator=_out._generator;
}
/* ------------------------------------------------------------ */
public void setCharacterEncoding(String encoding)
{
if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
{
_writeMode = WRITE_ISO1;
}
else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
{
_writeMode = WRITE_UTF8;
}
else
{
_writeMode = WRITE_CONV;
if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
_out._converter = null; // Set lazily in getConverter()
}
_out._characterEncoding = encoding;
if (_out._bytes==null)
_out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
}
/* ------------------------------------------------------------ */
public void close() throws IOException
{
_out.close();
}
/* ------------------------------------------------------------ */
public void flush() throws IOException
{
_out.flush();
}
/* ------------------------------------------------------------ */
public void write (String s,int offset, int length) throws IOException
{
while (length > MAX_OUTPUT_CHARS)
{
write(s, offset, MAX_OUTPUT_CHARS);
offset += MAX_OUTPUT_CHARS;
length -= MAX_OUTPUT_CHARS;
}
if (_out._chars==null)
{
_out._chars = new char[MAX_OUTPUT_CHARS];
}
char[] chars = _out._chars;
s.getChars(offset, offset + length, chars, 0);
write(chars, 0, length);
}
/* ------------------------------------------------------------ */
public void write (char[] s,int offset, int length) throws IOException
{
Output out = _out;
while (length > 0)
{
out._bytes.reset();
int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
switch (_writeMode)
{
case WRITE_CONV:
{
Writer converter=getConverter();
converter.write(s, offset, chars);
converter.flush();
}
break;
case WRITE_ISO1:
{
byte[] buffer=out._bytes.getBuf();
int bytes=out._bytes.getCount();
if (chars>buffer.length-bytes)
chars=buffer.length-bytes;
for (int i = 0; i < chars; i++)
{
int c = s[offset+i];
buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
}
if (bytes>=0)
out._bytes.setCount(bytes);
break;
}
case WRITE_UTF8:
{
byte[] buffer=out._bytes.getBuf();
int bytes=out._bytes.getCount();
if (bytes+chars>buffer.length)
chars=buffer.length-bytes;
for (int i = 0; i < chars; i++)
{
int code = s[offset+i];
if ((code & 0xffffff80) == 0)
{
// 1b
buffer[bytes++]=(byte)(code);
}
else if((code&0xfffff800)==0)
{
// 2b
if (bytes+2>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xc0|(code>>6));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
if (bytes+chars-i-1>buffer.length)
chars-=1;
}
else if((code&0xffff0000)==0)
{
// 3b
if (bytes+3>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xe0|(code>>12));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
if (bytes+chars-i-1>buffer.length)
chars-=2;
}
else if((code&0xff200000)==0)
{
// 4b
if (bytes+4>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xf0|(code>>18));
buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
if (bytes+chars-i-1>buffer.length)
chars-=3;
}
else if((code&0xf4000000)==0)
{
// 5b
if (bytes+5>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xf8|(code>>24));
buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
if (bytes+chars-i-1>buffer.length)
chars-=4;
}
else if((code&0x80000000)==0)
{
// 6b
if (bytes+6>buffer.length)
{
chars=i;
break;
}
buffer[bytes++]=(byte)(0xfc|(code>>30));
buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
buffer[bytes++]=(byte)(0x80|(code&0x3f));
if (bytes+chars-i-1>buffer.length)
chars-=5;
}
else
{
buffer[bytes++]=(byte)('?');
}
}
out._bytes.setCount(bytes);
break;
}
default:
throw new IllegalStateException();
}
out._bytes.writeTo(out);
length-=chars;
offset+=chars;
}
}
/* ------------------------------------------------------------ */
private Writer getConverter() throws IOException
{
if (_out._converter == null)
_out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
return _out._converter;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy