Request类是一个final类,该类是一个高效的server请求。
该类实例化之后许多参数不会被GC回收。同时该类所拥有的信息不会在初始化的时候就转换成可被用户直接使用的字符串的形式,大部分是以MessageBytes的形式存放,在被用户使用的时候才会进行转换,这样就大大提高了服务器的处理效率。
// ----------------------------------------------------------- Constructors
/**
* 构造方法
*/
public Request() {
parameters.setQuery(queryMB);
parameters.setURLDecoder(urlDecoder);
}
// ----------------------------------------------------- Instance Variables
/**
* 变量
*/
private int serverPort = -1;
// 服务器名称
private MessageBytes serverNameMB = MessageBytes.newInstance();
// 远程的端口
private int remotePort;
// 本地的端口
private int localPort;
// 请求策略
private MessageBytes schemeMB = MessageBytes.newInstance();
// 请求方法
private MessageBytes methodMB = MessageBytes.newInstance();
// 未处理的URI
private MessageBytes unparsedURIMB = MessageBytes.newInstance();
// URI
private MessageBytes uriMB = MessageBytes.newInstance();
// 转码之后的URI
private MessageBytes decodedUriMB = MessageBytes.newInstance();
// 请求
private MessageBytes queryMB = MessageBytes.newInstance();
// 原型
private MessageBytes protoMB = MessageBytes.newInstance();
// 远程地址
private MessageBytes remoteAddrMB = MessageBytes.newInstance();
private MessageBytes localNameMB = MessageBytes.newInstance();
private MessageBytes remoteHostMB = MessageBytes.newInstance();
private MessageBytes localAddrMB = MessageBytes.newInstance();
// http头部
private MimeHeaders headers = new MimeHeaders();
private MessageBytes instanceId = MessageBytes.newInstance();
/**
* Notes.
*/
private Object notes[] = new Object[Constants.MAX_NOTES];
/**
* 关联的输入缓存
*/
private InputBuffer inputBuffer = null;
/**
* URL转码器.
*/
private UDecoder urlDecoder = new UDecoder();
/**
* HTTP的常量
*/
private long contentLength = -1;
private MessageBytes contentTypeMB = null;
private String charEncoding = null;
private Cookies cookies = new Cookies(headers);
private Parameters parameters = new Parameters();
private MessageBytes remoteUser = MessageBytes.newInstance();
private MessageBytes authType = MessageBytes.newInstance();
private HashMap<String, Object> attributes = new HashMap<String, Object>();
private Response response;
private ActionHook hook;
private int bytesRead = 0;
// Time of the request - useful to avoid repeated calls to
// System.currentTime
private long startTime = -1;
private int available = 0;
private RequestInfo reqProcessorMX = new RequestInfo(this);
// ------------------------------------------------------------- Properties
/**
* Get the instance id (or JVM route). Currently Ajp is sending it with each
* request. In future this should be fixed, and sent only once ( or
* 'negotiated' at config time so both tomcat and apache share the same
* name.
*
* @return the instance id
*/
public MessageBytes instanceId() {
return instanceId;
}
public MimeHeaders getMimeHeaders() {
return headers;
}
public UDecoder getURLDecoder() {
return urlDecoder;
}
// -------------------- Request data --------------------
/**
* 请求数据
*
* @return
*/
public MessageBytes scheme() {
return schemeMB;
}
public MessageBytes method() {
return methodMB;
}
public MessageBytes unparsedURI() {
return unparsedURIMB;
}
public MessageBytes requestURI() {
return uriMB;
}
public MessageBytes decodedURI() {
return decodedUriMB;
}
public MessageBytes queryString() {
return queryMB;
}
public MessageBytes protocol() {
return protoMB;
}
/**
* Return the buffer holding the server name, if any. Use isNull() to check
* if there is no value set. This is the "virtual host", derived from the
* Host: header.
*/
public MessageBytes serverName() {
return serverNameMB;
}
public int getServerPort() {
return serverPort;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public MessageBytes remoteAddr() {
return remoteAddrMB;
}
public MessageBytes remoteHost() {
return remoteHostMB;
}
public MessageBytes localName() {
return localNameMB;
}
public MessageBytes localAddr() {
return localAddrMB;
}
public int getRemotePort() {
return remotePort;
}
public void setRemotePort(int port) {
this.remotePort = port;
}
public int getLocalPort() {
return localPort;
}
public void setLocalPort(int port) {
this.localPort = port;
}
// -------------------- encoding/type --------------------
/**
* Get the character encoding used for this request.
*/
public String getCharacterEncoding() {
if (charEncoding != null)
return charEncoding;
charEncoding = ContentType.getCharsetFromContentType(getContentType());
return charEncoding;
}
public void setCharacterEncoding(String enc) {
this.charEncoding = enc;
}
public void setContentLength(long len) {
this.contentLength = len;
}
public int getContentLength() {
long length = getContentLengthLong();
if (length < Integer.MAX_VALUE) {
return (int) length;
}
return -1;
}
public long getContentLengthLong() {
if (contentLength > -1)
return contentLength;
MessageBytes clB = headers.getUniqueValue("content-length");
contentLength = (clB == null || clB.isNull()) ? -1 : clB.getLong();
return contentLength;
}
public String getContentType() {
contentType();
if ((contentTypeMB == null) || contentTypeMB.isNull())
return null;
return contentTypeMB.toString();
}
public void setContentType(String type) {
contentTypeMB.setString(type);
}
public MessageBytes contentType() {
if (contentTypeMB == null)
contentTypeMB = headers.getValue("content-type");
return contentTypeMB;
}
public void setContentType(MessageBytes mb) {
contentTypeMB = mb;
}
public String getHeader(String name) {
return headers.getHeader(name);
}
// -------------------- Associated response --------------------
public Response getResponse() {
return response;
}
public void setResponse(Response response) {
this.response = response;
response.setRequest(this);
}
public void action(ActionCode actionCode, Object param) {
if (hook == null && response != null)
hook = response.getHook();
if (hook != null) {
if (param == null)
hook.action(actionCode, this);
else
hook.action(actionCode, param);
}
}
// -------------------- Cookies --------------------
public Cookies getCookies() {
return cookies;
}
// -------------------- Parameters --------------------
public Parameters getParameters() {
return parameters;
}
// -------------------- Other attributes --------------------
// We can use notes for most - need to discuss what is of general interest
public void setAttribute(String name, Object o) {
attributes.put(name, o);
}
public HashMap<String, Object> getAttributes() {
return attributes;
}
public Object getAttribute(String name) {
return attributes.get(name);
}
public MessageBytes getRemoteUser() {
return remoteUser;
}
public MessageBytes getAuthType() {
return authType;
}
public int getAvailable() {
return available;
}
public void setAvailable(int available) {
this.available = available;
}
// -------------------- Input Buffer --------------------
public InputBuffer getInputBuffer() {
return inputBuffer;
}
public void setInputBuffer(InputBuffer inputBuffer) {
this.inputBuffer = inputBuffer;
}
/**
* Read data from the input buffer and put it into a byte chunk.
*
* The buffer is owned by the protocol implementation - it will be reused on
* the next read. The Adapter must either process the data in place or copy
* it to a separate buffer if it needs to hold it. In most cases this is
* done during byte->char conversions or via InputStream. Unlike
* InputStream, this interface allows the app to process data in place,
* without copy.
*
*/
public int doRead(ByteChunk chunk) throws IOException {
int n = inputBuffer.doRead(chunk, this);
if (n > 0) {
bytesRead += n;
}
return n;
}
// -------------------- debug --------------------
@Override
public String toString() {
return "R( " + requestURI().toString() + ")";
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
// -------------------- Per-Request "notes" --------------------
/**
* Used to store private data. Thread data could be used instead - but if
* you have the req, getting/setting a note is just a array access, may be
* faster than ThreadLocal for very frequent operations.
*
* Example use: Jk: HandlerRequest.HOSTBUFFER = 10 CharChunk, buffer for
* Host decoding WorkerEnv: SSL_CERT_NOTE=16 - MessageBytes containing the
* cert
*
* Catalina CoyoteAdapter: ADAPTER_NOTES = 1 - stores the HttpServletRequest
* object ( req/res)
*
* To avoid conflicts, note in the range 0 - 8 are reserved for the servlet
* container ( catalina connector, etc ), and values in 9 - 16 for connector
* use.
*
* 17-31 range is not allocated or used.
*/
public final void setNote(int pos, Object value) {
notes[pos] = value;
}
public final Object getNote(int pos) {
return notes[pos];
}
// -------------------- Recycling --------------------
public void recycle() {
bytesRead = 0;
contentLength = -1;
contentTypeMB = null;
charEncoding = null;
headers.recycle();
serverNameMB.recycle();
serverPort = -1;
localNameMB.recycle();
localPort = -1;
remotePort = -1;
available = 0;
cookies.recycle();
parameters.recycle();
unparsedURIMB.recycle();
uriMB.recycle();
decodedUriMB.recycle();
queryMB.recycle();
methodMB.recycle();
protoMB.recycle();
schemeMB.recycle();
instanceId.recycle();
remoteUser.recycle();
authType.recycle();
attributes.clear();
startTime = -1;
}
// -------------------- Info --------------------
public void updateCounters() {
reqProcessorMX.updateCounters();
}
public RequestInfo getRequestProcessor() {
return reqProcessorMX;
}
public int getBytesRead() {
return bytesRead;
}
public boolean isProcessing() {
return reqProcessorMX.getStage() == org.apache.coyote.Constants.STAGE_SERVICE;
}
Response类也是一个final类。
// ----------------------------------------------------- Class Variables
/**
* Default locale as mandated by the spec.
*/
private static Locale DEFAULT_LOCALE = Locale.getDefault();
// ----------------------------------------------------- Instance Variables
/**
* Status code.
*/
protected int status = 200;
/**
* Status message.
*/
protected String message = null;
/**
* Response headers.
*/
protected MimeHeaders headers = new MimeHeaders();
/**
* Associated output buffer.
*/
protected OutputBuffer outputBuffer;
/**
* Notes.
*/
protected Object notes[] = new Object[Constants.MAX_NOTES];
/**
* Committed flag.
*/
protected boolean commited = false;
/**
* Action hook.
*/
public ActionHook hook;
/**
* HTTP specific fields.
*/
protected String contentType = null;
protected String contentLanguage = null;
protected String characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
protected long contentLength = -1;
private Locale locale = DEFAULT_LOCALE;
// General informations
private long contentWritten = 0;
private long commitTime = -1;
/**
* Holds request error exception.
*/
protected Exception errorException = null;
/**
* Has the charset been explicitly set.
*/
protected boolean charsetSet = false;
protected Request req;
// ------------------------------------------------------------- Properties
public Request getRequest() {
return req;
}
public void setRequest( Request req ) {
this.req=req;
}
public OutputBuffer getOutputBuffer() {
return outputBuffer;
}
public void setOutputBuffer(OutputBuffer outputBuffer) {
this.outputBuffer = outputBuffer;
}
public MimeHeaders getMimeHeaders() {
return headers;
}
public ActionHook getHook() {
return hook;
}
public void setHook(ActionHook hook) {
this.hook = hook;
}
// -------------------- Per-Response "notes" --------------------
public final void setNote(int pos, Object value) {
notes[pos] = value;
}
public final Object getNote(int pos) {
return notes[pos];
}
// -------------------- Actions --------------------
public void action(ActionCode actionCode, Object param) {
if (hook != null) {
if( param==null )
hook.action(actionCode, this);
else
hook.action(actionCode, param);
}
}
// -------------------- State --------------------
public int getStatus() {
return status;
}
/**
* Set the response status
*/
public void setStatus( int status ) {
this.status = status;
}
/**
* Get the status message.
*/
public String getMessage() {
return message;
}
/**
* Set the status message.
*/
public void setMessage(String message) {
this.message = message;
}
public boolean isCommitted() {
return commited;
}
public void setCommitted(boolean v) {
if (v && !this.commited) {
this.commitTime = System.currentTimeMillis();
}
this.commited = v;
}
/**
* Return the time the response was committed (based on System.currentTimeMillis).
*
* @return the time the response was committed
*/
public long getCommitTime() {
return commitTime;
}
// -----------------Error State --------------------
/**
* Set the error Exception that occurred during
* request processing.
*/
public void setErrorException(Exception ex) {
errorException = ex;
}
/**
* Get the Exception that occurred during request
* processing.
*/
public Exception getErrorException() {
return errorException;
}
public boolean isExceptionPresent() {
return ( errorException != null );
}
// -------------------- Methods --------------------
public void reset() throws IllegalStateException {
if (commited) {
throw new IllegalStateException();
}
recycle();
// Reset the stream
action(ActionCode.RESET, this);
}
public void finish() {
action(ActionCode.CLOSE, this);
}
public void acknowledge() {
action(ActionCode.ACK, this);
}
// -------------------- Headers --------------------
/**
* Warning: This method always returns <code>false<code> for Content-Type
* and Content-Length.
*/
public boolean containsHeader(String name) {
return headers.getHeader(name) != null;
}
public void setHeader(String name, String value) {
char cc=name.charAt(0);
if( cc=='C' || cc=='c' ) {
if( checkSpecialHeader(name, value) )
return;
}
headers.setValue(name).setString( value);
}
public void addHeader(String name, String value) {
char cc=name.charAt(0);
if( cc=='C' || cc=='c' ) {
if( checkSpecialHeader(name, value) )
return;
}
headers.addValue(name).setString( value );
}
/**
* Set internal fields for special header names.
* Called from set/addHeader.
* Return true if the header is special, no need to set the header.
*/
private boolean checkSpecialHeader( String name, String value) {
// XXX Eliminate redundant fields !!!
// ( both header and in special fields )
if( name.equalsIgnoreCase( "Content-Type" ) ) {
setContentType( value );
return true;
}
if( name.equalsIgnoreCase( "Content-Length" ) ) {
try {
long cL=Long.parseLong( value );
setContentLength( cL );
return true;
} catch( NumberFormatException ex ) {
// Do nothing - the spec doesn't have any "throws"
// and the user might know what he's doing
return false;
}
}
if( name.equalsIgnoreCase( "Content-Language" ) ) {
// XXX XXX Need to construct Locale or something else
}
return false;
}
/** Signal that we're done with the headers, and body will follow.
* Any implementation needs to notify ContextManager, to allow
* interceptors to fix headers.
*/
public void sendHeaders() {
action(ActionCode.COMMIT, this);
setCommitted(true);
}
// -------------------- I18N --------------------
public Locale getLocale() {
return locale;
}
/**
* Called explicitly by user to set the Content-Language and
* the default encoding
*/
public void setLocale(Locale locale) {
if (locale == null) {
return; // throw an exception?
}
// Save the locale for use by getLocale()
this.locale = locale;
// Set the contentLanguage for header output
contentLanguage = locale.getLanguage();
if ((contentLanguage != null) && (contentLanguage.length() > 0)) {
String country = locale.getCountry();
StringBuilder value = new StringBuilder(contentLanguage);
if ((country != null) && (country.length() > 0)) {
value.append('-');
value.append(country);
}
contentLanguage = value.toString();
}
}
/**
* Return the content language.
*/
public String getContentLanguage() {
return contentLanguage;
}
/*
* Overrides the name of the character encoding used in the body
* of the response. This method must be called prior to writing output
* using getWriter().
*
* @param charset String containing the name of the character encoding.
*/
public void setCharacterEncoding(String charset) {
if (isCommitted())
return;
if (charset == null)
return;
characterEncoding = charset;
charsetSet=true;
}
public String getCharacterEncoding() {
return characterEncoding;
}
/**
* 设置响应类型.
*
* This method must preserve any response charset that may already have
* been set via a call to response.setContentType(), response.setLocale(),
* or response.setCharacterEncoding().
*
* @param type the content type
*/
public void setContentType(String type) {
if (type == null) {
this.contentType = null;
return;
}
MediaType m = null;
try {
m = HttpParser.parseMediaType(new StringReader(type));
} catch (IOException e) {
// Ignore - null test below handles this
}
if (m == null) {
// Invalid - Assume no charset and just pass through whatever
// the user provided.
this.contentType = type;
return;
}
this.contentType = m.toStringNoCharset();
String charsetValue = m.getCharset();
if (charsetValue != null) {
charsetValue = charsetValue.trim();
if (charsetValue.length() > 0) {
charsetSet = true;
this.characterEncoding = charsetValue;
}
}
}
public void setContentTypeNoCharset(String type) {
this.contentType = type;
}
public String getContentType() {
String ret = contentType;
if (ret != null
&& characterEncoding != null
&& charsetSet) {
ret = ret + ";charset=" + characterEncoding;
}
return ret;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public int getContentLength() {
long length = getContentLengthLong();
if (length < Integer.MAX_VALUE) {
return (int) length;
}
return -1;
}
public long getContentLengthLong() {
return contentLength;
}
/**
* Write a chunk of bytes.
*/
public void doWrite(ByteChunk chunk/*byte buffer[], int pos, int count*/)
throws IOException
{
outputBuffer.doWrite(chunk, this);
contentWritten+=chunk.getLength();
}
// --------------------
public void recycle() {
contentType = null;
contentLanguage = null;
locale = DEFAULT_LOCALE;
characterEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
charsetSet = false;
contentLength = -1;
status = 200;
message = null;
commited = false;
commitTime = -1;
errorException = null;
headers.clear();
// update counters
contentWritten=0;
}
/**
* Bytes written by application - i.e. before compression, chunking, etc.
*/
public long getContentWritten() {
return contentWritten;
}
/**
* Bytes written to socket - i.e. after compression, chunking, etc.
*/
public long getBytesWritten(boolean flush) {
if (flush) {
action(ActionCode.CLIENT_FLUSH, this);
}
return outputBuffer.getBytesWritten();
}
RequestInfo类保存了Requet和Response对象的结构
RequestGroupInfo global = null;
// ----------------------------------------------------------- Constructors
/**
* 构造方法
* @param req
*/
public RequestInfo(Request req) {
this.req = req;
}
public RequestGroupInfo getGlobalProcessor() {
return global;
}
public void setGlobalProcessor(RequestGroupInfo global) {
if (global != null) {
this.global = global;
global.addRequestProcessor(this);
} else {
if (this.global != null) {
this.global.removeRequestProcessor(this);
this.global = null;
}
}
}
// ----------------------------------------------------- Instance Variables
/**
* 变量
*/
Request req;
int stage = Constants.STAGE_NEW;
String workerThreadName;
ObjectName rpName;
// -------------------- Information about the current request -----------
// This is useful for long-running requests only
public String getMethod() {
return req.method().toString();
}
public String getCurrentUri() {
return req.requestURI().toString();
}
public String getCurrentQueryString() {
return req.queryString().toString();
}
public String getProtocol() {
return req.protocol().toString();
}
public String getVirtualHost() {
return req.serverName().toString();
}
public int getServerPort() {
return req.getServerPort();
}
public String getRemoteAddr() {
req.action(ActionCode.REQ_HOST_ADDR_ATTRIBUTE, null);
return req.remoteAddr().toString();
}
/**
* Obtain the remote address for this connection as reported by an
* intermediate proxy (if any).
*/
public String getRemoteAddrForwarded() {
String remoteAddrProxy = (String) req
.getAttribute(Constants.REMOTE_ADDR_ATTRIBUTE);
if (remoteAddrProxy == null) {
return getRemoteAddr();
}
return remoteAddrProxy;
}
public int getContentLength() {
return req.getContentLength();
}
public long getRequestBytesReceived() {
return req.getBytesRead();
}
public long getRequestBytesSent() {
return req.getResponse().getContentWritten();
}
public long getRequestProcessingTime() {
if (getStage() == org.apache.coyote.Constants.STAGE_ENDED)
return 0;
else
return (System.currentTimeMillis() - req.getStartTime());
}
// -------------------- Statistical data --------------------
// Collected at the end of each request.
// 在每个请求的最后收集
private long bytesSent;
private long bytesReceived;
// Total time = divide by requestCount to get average.
private long processingTime;
// The longest response time for a request
private long maxTime;
// URI of the request that took maxTime
private String maxRequestUri;
private int requestCount;
// number of response codes >= 400
private int errorCount;
// the time of the last request
private long lastRequestProcessingTime = 0;
/**
* 在请求被回收之前由Processor调用。它将收集一些静态信息
* Called by the processor before recycling the request. It'll collect
* statistic information.
*/
void updateCounters() {
bytesReceived += req.getBytesRead();
bytesSent += req.getResponse().getContentWritten();
requestCount++;
if (req.getResponse().getStatus() >= 400)
errorCount++;
long t0 = req.getStartTime();
long t1 = System.currentTimeMillis();
long time = t1 - t0;
this.lastRequestProcessingTime = time;
processingTime += time;
if (maxTime < time) {
maxTime = time;
maxRequestUri = req.requestURI().toString();
}
}
public int getStage() {
return stage;
}
public void setStage(int stage) {
this.stage = stage;
}
public long getBytesSent() {
return bytesSent;
}
public void setBytesSent(long bytesSent) {
this.bytesSent = bytesSent;
}
public long getBytesReceived() {
return bytesReceived;
}
public void setBytesReceived(long bytesReceived) {
this.bytesReceived = bytesReceived;
}
public long getProcessingTime() {
return processingTime;
}
public void setProcessingTime(long processingTime) {
this.processingTime = processingTime;
}
public long getMaxTime() {
return maxTime;
}
public void setMaxTime(long maxTime) {
this.maxTime = maxTime;
}
public String getMaxRequestUri() {
return maxRequestUri;
}
public void setMaxRequestUri(String maxRequestUri) {
this.maxRequestUri = maxRequestUri;
}
public int getRequestCount() {
return requestCount;
}
public void setRequestCount(int requestCount) {
this.requestCount = requestCount;
}
public int getErrorCount() {
return errorCount;
}
public void setErrorCount(int errorCount) {
this.errorCount = errorCount;
}
public String getWorkerThreadName() {
return workerThreadName;
}
public ObjectName getRpName() {
return rpName;
}
public long getLastRequestProcessingTime() {
return lastRequestProcessingTime;
}
public void setWorkerThreadName(String workerThreadName) {
this.workerThreadName = workerThreadName;
}
public void setRpName(ObjectName rpName) {
this.rpName = rpName;
}
public void setLastRequestProcessingTime(long lastRequestProcessingTime) {
this.lastRequestProcessingTime = lastRequestProcessingTime;
}