在写servlet的时候,会继承HttpServlet,重写它的doPost、doGet(),这两个方法都有两个参数HttpServletRequest、HttpServletResponse。request中保存的是请求报文的信息。以getParameter()为例,看看数据是如何在在两个servlet中传递的。
一、request的来源
tomcat最终调用到我们自己编写的servelt是在ApplicationFilterChain.internalDoFilter()中,执行完所有过滤器后再执行servlet的service方法:
servlet.service((HttpServletRequest) request,
(HttpServletResponse) response);
此处的request是ApplicationFilterChain.doFilter()传递进来的。而doFilter方法是在StandardWrapperValve.invoke()中调用的:
filterChain.doFilter(request.getRequest(),
response.getResponse());
而request做为StandardWrapperValve.invoke的参数,其来源按请求处理的顺序可以追溯到CoyoteAdapter.service中:
connector.getContainer().getPipeline().getFirst().invoke(request, response);
此request为org.apache.catalina.connector.Request对象:
request = (Request) connector.createRequest();
request.setCoyoteRequest(req);
而这个req为org.apache.coyote.Request对象。
二、Requst似乎找到源头了,org.apache.catalina.connector.Request.getParameter():
public String getParameter(String name) {
if (!parametersParsed)
parseParameters();
return coyoteRequest.getParameters().getParameter(name);
}
如果没有解析过参数,则先解析parseParameters():
protected void parseParameters() {
parametersParsed = true;
Parameters parameters = coyoteRequest.getParameters();
// Set this every time in case limit has been changed via JMX
parameters.setLimit(getConnector().getMaxParameterCount());
// getCharacterEncoding() may have been overridden to search for
// hidden form field containing request encoding
String enc = getCharacterEncoding();
boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
if (enc != null) {
parameters.setEncoding(enc);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding(enc);
}
} else {
parameters.setEncoding
(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
if (useBodyEncodingForURI) {
parameters.setQueryStringEncoding
(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING);
}
}
parameters.handleQueryParameters();
if (usingInputStream || usingReader)
return;
if (!getMethod().equalsIgnoreCase("POST"))
return;
String contentType = getContentType();
if (contentType == null)
contentType = "";
int semicolon = contentType.indexOf(';');
if (semicolon >= 0) {
contentType = contentType.substring(0, semicolon).trim();
} else {
contentType = contentType.trim();
}
if (!("application/x-www-form-urlencoded".equals(contentType)))
return;
boolean success = false;
int len = getContentLength();
try {
if (len > 0) {
int maxPostSize = connector.getMaxPostSize();
if ((maxPostSize > 0) && (len > maxPostSize)) {
if (context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.postTooLarge"));
}
return;
}
byte[] formData = null;
if (len < CACHED_POST_LEN) {
if (postData == null)
postData = new byte[CACHED_POST_LEN];
formData = postData;
} else {
formData = new byte[len];
}
try {
if (readPostBody(formData, len) != len) {
return;
}
} catch (IOException e) {
// Client disconnect
if (context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"), e);
}
return;
}
parameters.processParameters(formData, 0, len);
} else if ("chunked".equalsIgnoreCase(
coyoteRequest.getHeader("transfer-encoding"))) {
byte[] formData = null;
try {
formData = readChunkedPostBody();
} catch (IOException e) {
// Client disconnect or chunkedPostTooLarge error
if (context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"), e);
}
return;
}
if (formData != null) {
parameters.processParameters(formData, 0, formData.length);
}
}
success = true;
} finally {
if (!success) {
parameters.setParseFailed(true);
}
}
}
parameters.handleQueryParameters();解析的是通过get方法传递的参数(将参数添加在请求地址后)。Parameter.handleQueryParameters():
public void handleQueryParameters() {
if( didQueryParameters ) return;
didQueryParameters=true;
if( queryMB==null || queryMB.isNull() )
return;
if(log.isDebugEnabled()) {
log.debug("Decoding query " + decodedQuery + " " +
queryStringEncoding);
}
try {
decodedQuery.duplicate( queryMB );
} catch (IOException e) {
// Can't happen, as decodedQuery can't overflow
e.printStackTrace();
}
processParameters( decodedQuery, queryStringEncoding );
}
处理的queryMB中的数据,queryMB是什么时候赋值的呢?queryMB是parameter的属性,而parameter是coyoteRequest的属性,coyoteRequest来自CoyoteAdapter.service():
request.setCoyoteRequest(req);
CoyoteAdapter.service()由Http11Processer.process()调用。req来自Http11Processer的构造函数中:
request = new Request();
inputBuffer = new InternalInputBuffer(request, headerBufferSize);
request.setInputBuffer(inputBuffer);
Http11Processer.process():
inputBuffer.parseRequestLine();
request.setStartTime(System.currentTimeMillis());
keptAlive = true;
if (disableUploadTimeout) {
socket.setSoTimeout(soTimeout);
} else {
socket.setSoTimeout(timeout);
}
// Set this every time in case limit has been changed via JMX
request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
inputBuffer.parseHeaders();
inputBuffer.parseRequestLine();将截取请求报文的第一行进行解析,而inputBuffer.parseHeaders()即解析其他报文头信息。先看看一个简单的socket请求报文:
GET /helloServlet?user=World HTTP/1.1
accept:image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
referer:http://localhost:8080/formTest.html
accept-language:zh-CN
user-agent:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)
accept-encoding:gzip, deflate
host:localhost:8080
connection:Keep-Alive
InternalInputBuffer.parseRequestLine()将解析“GET /helloServlet?user=World HTTP/1.1”
public void parseRequestLine()
throws IOException {
int start = 0;
//
// Skipping blank lines
//
byte chr = 0;
do {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
chr = buf[pos++];
} while ((chr == Constants.CR) || (chr == Constants.LF));
pos--;
// Mark the current buffer position
start = pos;
//
// Reading the method name
// Method name is always US-ASCII
//
boolean space = false;
while (!space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
// Spec says no CR or LF in method name
if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
throw new IllegalArgumentException(
sm.getString("iib.invalidmethod"));
}
// Spec says single SP but it also says be tolerant of HT
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
space = true;
request.method().setBytes(buf, start, pos - start);
}
pos++;
}
// Spec says single SP but also says be tolerant of multiple and/or HT
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
pos++;
} else {
space = false;
}
}
// Mark the current buffer position
start = pos;
int end = 0;
int questionPos = -1;
//
// Reading the URI
//
boolean eol = false;
while (!space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
// Spec says single SP but it also says be tolerant of HT
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
space = true;
end = pos;
} else if ((buf[pos] == Constants.CR)
|| (buf[pos] == Constants.LF)) {
// HTTP/0.9 style request
eol = true;
space = true;
end = pos;
} else if ((buf[pos] == Constants.QUESTION)
&& (questionPos == -1)) {
questionPos = pos;
}
pos++;
}
request.unparsedURI().setBytes(buf, start, end - start);
if (questionPos >= 0) {
request.queryString().setBytes(buf, questionPos + 1,
end - questionPos - 1);
request.requestURI().setBytes(buf, start, questionPos - start);
} else {
request.requestURI().setBytes(buf, start, end - start);
}
// Spec says single SP but also says be tolerant of multiple and/or HT
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
pos++;
} else {
space = false;
}
}
// Mark the current buffer position
start = pos;
end = 0;
//
// Reading the protocol
// Protocol is always US-ASCII
//
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.CR) {
end = pos;
} else if (buf[pos] == Constants.LF) {
if (end == 0)
end = pos;
eol = true;
}
pos++;
}
if ((end - start) > 0) {
request.protocol().setBytes(buf, start, end - start);
} else {
request.protocol().setString("");
}
}
方法中的request即org.apache.coyote.Request,在Http11Processor构造方法中就已经设置好了。queryMB就在这里设置的:
request.queryString().setBytes(buf, questionPos + 1,
end - questionPos - 1);
org.apache.coyote.Request的构造方法就将queryMB和parameter关联了:
public Request() {
parameters.setQuery(queryMB);
parameters.setURLDecoder(urlDecoder);
}
所以这样queryMB就保存到了parameter对象中了。Parameter.handleQueryParameters()中调用processParameters( decodedQuery, queryStringEncoding )进行处理:
public void processParameters( MessageBytes data, String encoding ) {
if( data==null || data.isNull() || data.getLength() <= 0 ) return;
if( data.getType() != MessageBytes.T_BYTES ) {
data.toBytes();
}
ByteChunk bc=data.getByteChunk();
processParameters( bc.getBytes(), bc.getOffset(),
bc.getLength(), getCharset(encoding));
}
进而调用重构方法processParameters( bc.getBytes(), bc.getOffset(), bc.getLength(), getCharset(encoding));
private void processParameters(byte bytes[], int start, int len,
Charset charset) {
if(log.isDebugEnabled()) {
try {
log.debug(sm.getString("parameters.bytes",
new String(bytes, start, len, DEFAULT_CHARSET.name())));
} catch (UnsupportedEncodingException uee) {
// Not possible. All JVMs must support ISO-8859-1
}
}
int decodeFailCount = 0;
int pos = start;
int end = start + len;
while(pos < end) {
parameterCount ++;
if (limit > -1 && parameterCount > limit) {
parseFailed = true;
log.info(sm.getString("parameters.maxCountFail",
Integer.valueOf(limit)));
break;
}
int nameStart = pos;
int nameEnd = -1;
int valueStart = -1;
int valueEnd = -1;
boolean parsingName = true;
boolean decodeName = false;
boolean decodeValue = false;
boolean parameterComplete = false;
do {
switch(bytes[pos]) {
case '=':
if (parsingName) {
// Name finished. Value starts from next character
nameEnd = pos;
parsingName = false;
valueStart = ++pos;
} else {
// Equals character in value
pos++;
}
break;
case '&':
if (parsingName) {
// Name finished. No value.
nameEnd = pos;
} else {
// Value finished
valueEnd = pos;
}
parameterComplete = true;
pos++;
break;
case '%':
case '+':
// Decoding required
if (parsingName) {
decodeName = true;
} else {
decodeValue = true;
}
pos ++;
break;
default:
pos ++;
break;
}
} while (!parameterComplete && pos < end);
if (pos == end) {
if (nameEnd == -1) {
nameEnd = pos;
} else if (valueStart > -1 && valueEnd == -1){
valueEnd = pos;
}
}
if (log.isDebugEnabled() && valueStart == -1) {
try {
log.debug(sm.getString("parameters.noequal",
Integer.valueOf(nameStart),
Integer.valueOf(nameEnd),
new String(bytes, nameStart, nameEnd-nameStart,
DEFAULT_CHARSET.name())));
} catch (UnsupportedEncodingException uee) {
// Not possible. All JVMs must support ISO-8859-1
}
}
if (nameEnd <= nameStart ) {
if (valueStart == -1) {
// &&
if (log.isDebugEnabled()) {
log.debug(sm.getString("parameters.emptyChunk"));
}
// Do not flag as error
continue;
}
// &=foo&
if (log.isInfoEnabled()) {
if (valueEnd >= nameStart && log.isDebugEnabled()) {
String extract = null;
try {
extract = new String(bytes, nameStart,
valueEnd - nameStart,
DEFAULT_CHARSET.name());
} catch (UnsupportedEncodingException uee) {
// Not possible. All JVMs must support ISO-8859-1
}
log.info(sm.getString("parameters.invalidChunk",
Integer.valueOf(nameStart),
Integer.valueOf(valueEnd),
extract));
} else {
log.info(sm.getString("parameters.invalidChunk",
Integer.valueOf(nameStart),
Integer.valueOf(nameEnd),
null));
}
}
parseFailed = true;
continue;
// invalid chunk - it's better to ignore
}
tmpName.setBytes(bytes, nameStart, nameEnd - nameStart);
if (valueStart >= 0) {
tmpValue.setBytes(bytes, valueStart, valueEnd - valueStart);
} else {
tmpValue.setBytes(bytes, 0, 0);
}
// Take copies as if anything goes wrong originals will be
// corrupted. This means original values can be logged.
// For performance - only done for debug
if (log.isDebugEnabled()) {
try {
origName.append(bytes, nameStart, nameEnd - nameStart);
if (valueStart >= 0) {
origValue.append(bytes, valueStart, valueEnd - valueStart);
} else {
origValue.append(bytes, 0, 0);
}
} catch (IOException ioe) {
// Should never happen...
log.error(sm.getString("parameters.copyFail"), ioe);
}
}
try {
String name;
String value;
if (decodeName) {
urlDecode(tmpName);
}
tmpName.setCharset(charset);
name = tmpName.toString();
if (valueStart >= 0) {
if (decodeValue) {
urlDecode(tmpValue);
}
tmpValue.setCharset(charset);
value = tmpValue.toString();
} else {
value = "";
}
addParam(name, value);
} catch (IOException e) {
parseFailed = true;
decodeFailCount++;
if (decodeFailCount == 1 || log.isDebugEnabled()) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("parameters.decodeFail.debug",
origName.toString(), origValue.toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("parameters.decodeFail.info",
tmpName.toString(), tmpValue.toString()), e);
}
}
}
tmpName.recycle();
tmpValue.recycle();
// Only recycle copies if we used them
if (log.isDebugEnabled()) {
origName.recycle();
origValue.recycle();
}
}
if (decodeFailCount > 1 && !log.isDebugEnabled()) {
log.info(sm.getString("parameters.multipleDecodingFail",
Integer.valueOf(decodeFailCount)));
}
}
解析出键值对后,调用addParam():
private void addParam( String key, String value ) {
if( key==null ) return;
ArrayList<String> values = paramHashValues.get(key);
if (values == null) {
values = new ArrayList<String>(1);
paramHashValues.put(key, values);
}
values.add(value);
}
parameter存储的数据结构是HashMap<String,ArrayList<String>>,如此相同键可以对应多个值,所以Request.getParameterValues()和Request.getParameter()区别是:前者以数组形式返回相同键的所有值,后者返回第一个值。
上面只弄明白了get方法传递的参数,那么post方法传递的参数是如何处理的?
post的报文:
POST /helloServlet HTTP/1.1
accept:image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
referer:http://localhost:8080/formTest.html
accept-language:zh-CN
user-agent:Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)
content-type:application/x-www-form-urlencoded
accept-encoding:gzip, deflate
host:localhost:8080
content-length:10
connection:Keep-Alive
cache-control:no-cache
cookie:JSESSIONID=7C7A6442B14FF1DE367AE6BC6D6A70F4
user=World
还是在org.apache.catalina.connector.parseParameters()中处理post请求的参数:
byte[] formData = null;
if (len < CACHED_POST_LEN) {
if (postData == null)
postData = new byte[CACHED_POST_LEN];
formData = postData;
} else {
formData = new byte[len];
}
try {
if (readPostBody(formData, len) != len) {
return;
}
} catch (IOException e) {
// Client disconnect
if (context.getLogger().isDebugEnabled()) {
context.getLogger().debug(
sm.getString("coyoteRequest.parseParameters"), e);
}
return;
}
parameters.processParameters(formData, 0, len);
readPostBod()将请求流中的报文体读到formData中:
protected int readPostBody(byte body[], int len)
throws IOException {
int offset = 0;
do {
int inputLen = getStream().read(body, offset, len - offset);
if (inputLen <= 0) {
return offset;
}
offset += inputLen;
} while ((len - offset) > 0);
String b = new String(body);
return len;
}
read方法从输入流中读取字节,只能往后读,前面经过InternalInputBuffer.parseRequestLine()和InternalInputBuffer.parseHeaders()方法已经将报文头解析完成,此时socket输入流中可用read方法直接读取报文体,其长度为报文头中的“content-length”。
参数的解析有点像懒加载,第一次用到才去解析,可以提高些效率。
org.apache.catalina.connector.Request类对org.apache.coyote.Request进行了封装,遵循Servlet API实现了HttpServletRequest接口。