在上一篇中,我们讲述了Tomcat在启动之初所做的事情,我们简单罗列回顾一下
1)依次开启组件,其中较为核心的是开启Connector的serverSocket,并等待请求
2)开新线程初始化容器,补充一点,之所以要开启新线程而不在主线程完成,个人理解核心原因是为了在Tomcat运行过程中可以直接添加新的应用
3)开启等待关闭线程,等待Tomcat的关闭指令
这一篇我们将着重讲解Tomcat在接收到新的Http请求的时候应该做的事情
首先我们明白一点,Http协议是一种基于TCP连接的应用层协议,而Socket又是基于TCP的一种横跨网络层、传输层、应用层的接口,而在Tomcat中完成网络连接的代码也基本都是由Socket实现的。
在上一篇中我们看到了JIoEndpoint中开启了一个监听Http连接端口(默认8080)的serverSocket,当有请求的时候,serverSocket会接收到一个socket,并将socket传入processSocket方法中处理:
Socket socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (setSocketOptions(socket)) {
// Hand this socket off to an appropriate processor
if (!processSocket(socket)) {
// Close socket right away
try {
socket.close();
} catch (IOException e) {
// Ignore
}
} else {
countUpConnection();
}
} else {
// Close socket right away
try {
socket.close();
} catch (IOException e) {
// Ignore
}
}
下面看下processSocket方法:
/**
* Process a new connection from a new client. Wraps the socket so
* keep-alive and other attributes can be tracked and then passes the socket
* to the executor for processing.
*
* @param socket The socket associated with the client.
*
* @return <code>true</code> if the socket is passed to the
* executor, <code>false</code> if something went wrong or
* if the endpoint is shutting down. Returning
* <code>false</code> is an indication to close the socket
* immediately.
*/
protected boolean processSocket(Socket socket) {
// Process the request from this socket
try {
SocketWrapper<Socket> wrapper = new SocketWrapper<Socket>(socket);
wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
// During shutdown, executor may be null - avoid NPE
if (!running) {
return false;
}
getExecutor().execute(new SocketProcessor(wrapper));
} catch (RejectedExecutionException x) {
log.warn("Socket processing request was rejected for:"+socket,x);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
log.error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
这里我们看到代码将Socket封装进了SocketWrapper中,并开启新线程SocketProcessor去处理请求:
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor implements Runnable {
protected SocketWrapper<Socket> socket = null;
protected SocketStatus status = null;
public SocketProcessor(SocketWrapper<Socket> socket) {
if (socket==null) throw new NullPointerException();
this.socket = socket;
}
public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) {
this(socket);
this.status = status;
}
@Override
public void run() {
boolean launch = false;
synchronized (socket) {
try {
SocketState state = SocketState.OPEN;
try {
// SSL handshake
serverSocketFactory.handshake(socket.getSocket());
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.err.handshake"), t);
}
// Tell to close the socket
state = SocketState.CLOSED;
}
if ( (state != SocketState.CLOSED) ) {
state = (status==null)?handler.process(socket):handler.process(socket,status);
}
if (state == SocketState.CLOSED) {
// Close socket
if (log.isTraceEnabled()) {
log.trace("Closing socket:"+socket);
}
countDownConnection();
try {
socket.getSocket().close();
} catch (IOException e) {
// Ignore
}
} else if (state == SocketState.ASYNC_END ||
state == SocketState.OPEN){
socket.setKeptAlive(true);
socket.access();
launch = true;
} else if (state == SocketState.LONG) {
socket.access();
waitingRequests.add(socket);
}
} finally {
if (launch) {
try {
getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN));
} catch (NullPointerException npe) {
if (running) {
log.error(sm.getString("endpoint.launch.fail"),
npe);
}
}
}
}
}
socket = null;
// Finish up this request
}
}
这里调用了handler去处理Socket,handler有两个实现类,分别处理AJP和HTTP,我们看到HTTP的:
@Override
public SocketState process(SocketWrapper<Socket> socket, SocketStatus status) {
Http11Processor processor = connections.remove(socket);
try {
if (processor == null) {
processor = recycledProcessors.poll();
}
if (processor == null) {
processor = createProcessor();
}
if (proto.isSSLEnabled() && (proto.sslImplementation != null)) {
processor.setSSLSupport(
proto.sslImplementation.getSSLSupport(
socket.getSocket()));
} else {
processor.setSSLSupport(null);
}
SocketState state = socket.isAsync()?processor.asyncDispatch(status):processor.process(socket);
if (state == SocketState.LONG) {
connections.put(socket, processor);
socket.setAsync(true);
// longPoll may change socket state (e.g. to trigger a
// complete or dispatch)
return processor.asyncPostProcess();
} else {
socket.setAsync(false);
recycledProcessors.offer(processor);
}
return state;
} catch(java.net.SocketException e) {
// SocketExceptions are normal
log.debug(sm.getString(
"http11protocol.proto.socketexception.debug"), e);
} catch (java.io.IOException e) {
// IOExceptions are normal
log.debug(sm.getString(
"http11protocol.proto.ioexception.debug"), e);
}
// Future developers: if you discover any other
// rare-but-nonfatal exceptions, catch them here, and log as
// above.
catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
// any other exception or error is odd. Here we log it
// with "ERROR" level, so it will show up even on
// less-than-verbose logs.
log.error(sm.getString("http11protocol.proto.error"), e);
}
recycledProcessors.offer(processor);
return SocketState.CLOSED;
}
到这里会觉得Tomcat的调用过程怎么那么绕,不过有些经验的开发人员都能意识到这其中深含的模块化思想,目前对Tomcat源码只是初窥门径,我在后面的学习中会整理出Tomcat这些处理过程的模块分化,先继续就着调用的顺序往下看,processor的process过程:
/**
* Process pipelined HTTP requests on the specified socket.
*
* @param socketWrapper Socket from which the HTTP requests will be read
* and the HTTP responses will be written.
*
* @throws IOException error during an I/O operation
*/
public SocketState process(SocketWrapper<Socket> socketWrapper)
throws IOException {
RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// Set the remote address
remoteAddr = null;
remoteHost = null;
localAddr = null;
localName = null;
remotePort = -1;
localPort = -1;
// Setting up the I/O
this.socket = socketWrapper;
inputBuffer.setInputStream(socket.getSocket().getInputStream());
outputBuffer.setOutputStream(socket.getSocket().getOutputStream());
// Error flag
error = false;
keepAlive = true;
int keepAliveLeft = maxKeepAliveRequests>0?socketWrapper.decrementKeepAlive():-1;
int soTimeout = endpoint.getSoTimeout();
try {
socket.getSocket().setSoTimeout(soTimeout);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.debug(sm.getString("http11processor.socket.timeout"), t);
error = true;
}
boolean keptAlive = socketWrapper.isKeptAlive();
while (!error && keepAlive && !endpoint.isPaused()) {
// Parsing the request header
try {
//TODO - calculate timeout based on length in queue (System.currentTimeMills() - wrapper.getLastAccess() is the time in queue)
if (keptAlive) {
if (keepAliveTimeout > 0) {
socket.getSocket().setSoTimeout(keepAliveTimeout);
}
else if (soTimeout > 0) {
socket.getSocket().setSoTimeout(soTimeout);
}
}
inputBuffer.parseRequestLine(false);
request.setStartTime(System.currentTimeMillis());
keptAlive = true;
if (disableUploadTimeout) {
socket.getSocket().setSoTimeout(soTimeout);
} else {
socket.getSocket().setSoTimeout(connectionUploadTimeout);
}
inputBuffer.parseHeaders();
} catch (IOException e) {
error = true;
break;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (log.isDebugEnabled()) {
log.debug(sm.getString("http11processor.header.parse"), t);
}
// 400 - Bad Request
response.setStatus(400);
adapter.log(request, response, 0);
error = true;
}
if (!error) {
// Setting up filters, and parse some request headers
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
try {
prepareRequest();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (log.isDebugEnabled()) {
log.debug(sm.getString("http11processor.request.prepare"), t);
}
// 400 - Internal Server Error
response.setStatus(400);
adapter.log(request, response, 0);
error = true;
}
}
if (maxKeepAliveRequests > 0 && keepAliveLeft == 0)
keepAlive = false;
// Process the request in the adapter
if (!error) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
adapter.service(request, response);
// Handle when the response was committed before a serious
// error occurred. Throwing a ServletException should both
// set the status to 500 and set the errorException.
// If we fail here, then the response is likely already
// committed, so we can't try and set headers.
if(keepAlive && !error) { // Avoid checking twice.
error = response.getErrorException() != null ||
statusDropsConnection(response.getStatus());
}
} catch (InterruptedIOException e) {
error = true;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("http11processor.request.process"), t);
// 500 - Internal Server Error
response.setStatus(500);
adapter.log(request, response, 0);
error = true;
}
}
// Finish the handling of the request
try {
rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
// If we know we are closing the connection, don't drain input.
// This way uploading a 100GB file doesn't tie up the thread
// if the servlet has rejected it.
if(error && !isAsync())
inputBuffer.setSwallowInput(false);
if (!isAsync())
endRequest();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("http11processor.request.finish"), t);
// 500 - Internal Server Error
response.setStatus(500);
adapter.log(request, response, 0);
error = true;
}
try {
rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("http11processor.response.finish"), t);
error = true;
}
// If there was an error, make sure the request is counted as
// and error, and update the statistics counter
if (error) {
response.setStatus(500);
}
request.updateCounters();
rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
// Don't reset the param - we'll see it as ended. Next request
// will reset it
// thrA.setParam(null);
// Next request
if (!isAsync() || error) {
inputBuffer.nextRequest();
outputBuffer.nextRequest();
}
//hack keep alive behavior
break;
}
rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
if (error || endpoint.isPaused()) {
recycle();
return SocketState.CLOSED;
} else if (isAsync()) {
return SocketState.LONG;
} else {
if (!keepAlive) {
recycle();
return SocketState.CLOSED;
} else {
return SocketState.OPEN;
}
}
}
这个方法比较长,我们看到在Processor中内置了inputBuffer、outputBuffer和request等对象,其中request和inputBuffer是个双向引用的关系:
request = new Request();
inputBuffer = new InternalInputBuffer(request, headerBufferSize);
request.setInputBuffer(inputBuffer);
这样就可以在处理的时候达到同时处理两个对象的作用,然后在处理过程中可以看到inputBuffer设置了socket的输入流,然后分别调用其parseRequestLine、parseHeaders方法,具体方法内容初略一看就是处理这些输入的字节,有兴趣的可以和Http协议一起做详细的研究,我们这就先不展开了,总之处理完输入流的请求,然后调用prepareRequest方法,由于这部分代码的处理和协议内容关联比较大,如果没有非常熟悉http的数据包结构可能很难直接看源代码读懂,我的方法是断点调试,分别看清楚头尾的request参数有哪些不同,并着重看参数在哪部分代码运行之后起到变化。个人觉得这段代码给request传的值是最明显的:
/**
* Parse host.
*/
protected void parseHost(MessageBytes valueMB) {
if (valueMB == null || valueMB.isNull()) {
// HTTP/1.0
// Default is what the socket tells us. Overridden if a host is
// found/parsed
request.setServerPort(socket.getSocket().getLocalPort());
InetAddress localAddress = socket.getSocket().getLocalAddress();
// Setting the socket-related fields. The adapter doesn't know
// about socket.
request.serverName().setString(localAddress.getHostName());
return;
}
总而言之在给输入流做了处理以后填装进request中,并把request、response全部通过adapter.service处理
/**
* Service method.
*/
@Override
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);
// Link objects
request.setResponse(response);
response.setRequest(request);
// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);
// Set query string encoding
req.getParameters().setQueryStringEncoding
(connector.getURIEncoding());
}
if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}
boolean comet = false;
boolean async = false;
try {
// Parse and set Catalina and configuration specific
// request parameters
req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
boolean postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
if (request.isComet()) {
if (!response.isClosed() && !response.isError()) {
if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
// Invoke a read event right away if there are available bytes
if (event(req, res, SocketStatus.OPEN)) {
comet = true;
res.action(ActionCode.COMET_BEGIN, null);
}
} else {
comet = true;
res.action(ActionCode.COMET_BEGIN, null);
}
} else {
// Clear the filter chain, as otherwise it will not be reset elsewhere
// since this is a Comet request
request.setFilterChain(null);
}
}
}
AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
if (asyncConImpl != null) {
async = true;
} else if (!comet) {
response.finishResponse();
if (postParseSuccess) {
// Log only if processing was invoked.
// If postParseRequest() failed, it has already logged it.
((Context) request.getMappingData().context).logAccess(
request, response,
System.currentTimeMillis() - req.getStartTime(),
false);
}
req.action(ActionCode.POST_REQUEST , null);
}
} catch (IOException e) {
// Ignore
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("coyoteAdapter.service"), t);
} finally {
req.getRequestProcessor().setWorkerThreadName(null);
// Recycle the wrapper request and response
if (!comet && !async) {
request.recycle();
response.recycle();
} else {
// Clear converters so that the minimum amount of memory
// is used by this processor
request.clearEncoders();
response.clearEncoders();
}
}
}
所谓adapter,顾名思义就是适配不同的接口,这里我们也看到了,两套不同的request和response在我们眼前展现,第一套是刚才传入的org.apache.coyote包下的Request和Response、第二套就是我们之后要进入下一步操作的org.apache.catalina.connector下的Request和Response,在这段代码中有两行非常重要:
boolean postParseSuccess = postParseRequest(req, request, res, response);
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
前者是映射内部参数,将上一步把socket输入流中传入的信息解析完成的request交给下一步调用的request处理,后一步则是调用接下来的过程。
我们先看前者,可能有人会要问为啥要那么麻烦开设两套request,其实一开始看代码的时候我也被两套请求类给搞懵了,由于没有对整个架构做过深层次分析,暂时还没有给这个问题准确的答案,但是我们可以提出假想来给后续的研究提供思路,据我而言,这种情况大致可能有两种:
第一种是Tomcat自己设计了一套请求类族,并完成了基本封装功能,但是由于这套类族不符合J2EE设计的类族规范,因此用适配器做适配,这也符合适配器模式本身的意图;
第二种是原本的第一次封装是在一个不分Http和AJP协议的Request中的,我们看Request的全名org.apache.coyote.Request,显然似乎没有做区分,而第二层封装,Request是实现了HttpServletRequest接口的,显然是专门为了Http协议设计的,因此过程中做适配,以达到第一层封装的可复用。
上述两种只是假想,先提出,后续研究来解答
我们继续看postParseRequest方法,由于这个方法比较长,我们截取较为重要的几句:
String proxyName = connector.getProxyName();
int proxyPort = connector.getProxyPort();
if (proxyPort != 0) {
req.setServerPort(proxyPort);
}
if (proxyName != null) {
req.serverName().setString(proxyName);
}
//...........
if (connector.getUseIPVHosts()) {
serverName = req.localName();
if (serverName.isNull()) {
// well, they did ask for it
res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
}
} else {
serverName = req.serverName();
}
//.............
connector.getMapper().map(serverName, decodedURI, version,
request.getMappingData());
request.setContext((Context) request.getMappingData().context);
request.setWrapper((Wrapper) request.getMappingData().wrapper);
上述代码可以看到,从第一个request中取出了serverName、url等信息之后转入map方法,然后给第二个request的mappingData做初始化,最后给request设置基本值,这里已经把Context和Wrapper两个容器都设置在了request中,后续将会有大用处。至于map方法调用来调用去的我们具体不说了大家自己看,简单说下就是还记得上一篇讲到容器启动时候初始化完成以后,会有一个mappingListener.init方法给容器进行注册吗?这里就是从注册的容器中选出对应的四个容器并传入request.mappingData。
然后我们再回头这段代码:connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
前两个调用很好理解,仔细看过上一篇就明白获取到的Container就是engine,而每个容器其实都有一个pipeline,这个pipeline顾名思义是管道的意思,它的命名来源从我阅读的博客来看,是将父子容器的调用关联起来,请求先通过父容器再传输到子容器,其过程就如同管道运输分流一般,而管道中获取valve之后调用invoke方法,由于有四个容器,那么自然有四个valve,我们来看两个,分别hostValve和wrapperValve:
/**
* Select the appropriate child Context to process this request,
* based on the specified request URI. If no matching Context can
* be found, return an appropriate HTTP error.
*
* @param request Request to be processed
* @param response Response to be produced
*
* @exception IOException if an input/output error occurred
* @exception ServletException if a servlet error occurred
*/
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Context to be used for this Request
Context context = request.getContext();
if (context == null) {
response.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
// Bind the context CL to the current thread
if( context.getLoader() != null ) {
// Not started - it should check for availability first
// This should eventually move to Engine, it's generic.
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> pa = new PrivilegedSetTccl(
context.getLoader().getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
}
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(context.getPipeline().isAsyncSupported());
}
// Ask this Context to process this request
context.getPipeline().getFirst().invoke(request, response);
// Access a session (if present) to update last accessed time, based on a
// strict interpretation of the specification
if (ACCESS_SESSION) {
request.getSession(false);
}
// Error page processing
response.setSuspended(false);
Throwable t = (Throwable) request.getAttribute(
RequestDispatcher.ERROR_EXCEPTION);
if (t != null) {
throwable(request, response, t);
} else {
status(request, response);
}
// Restore the context classloader
if (Globals.IS_SECURITY_ENABLED) {
PrivilegedAction<Void> pa = new PrivilegedSetTccl(
StandardHostValve.class.getClassLoader());
AccessController.doPrivileged(pa);
} else {
Thread.currentThread().setContextClassLoader
(StandardHostValve.class.getClassLoader());
}
}
host中很长一段,但是其核心主要就是context.getPipeline().getFirst().invoke(request, response);,从request中获取context(上述map过程中传入的),并调用子容器valve的invoke,这里也可以明白父子管道的连接是如何实现的。
最终自然调用到了wrapperValve:
/**
* Invoke the servlet we are managing, respecting the rules regarding
* servlet lifecycle and SingleThreadModel support.
*
* @param request Request to be processed
* @param response Response to be produced
*
* @exception IOException if an input/output error occurred
* @exception ServletException if a servlet error occurred
*/
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Initialize local variables we may need
boolean unavailable = false;
Throwable throwable = null;
// This should be a Request attribute...
long t1=System.currentTimeMillis();
requestCount++;
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
// Check for the application being marked unavailable
if (!context.getAvailable()) {
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardContext.isUnavailable"));
unavailable = true;
}
// Check for the servlet being marked unavailable
if (!unavailable && wrapper.isUnavailable()) {
container.getLogger().info(sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
unavailable = true;
}
// Allocate a servlet instance to process this request
try {
if (!unavailable) {
servlet = wrapper.allocate();
}
} catch (UnavailableException e) {
container.getLogger().error(
sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
} catch (ServletException e) {
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), StandardWrapper.getRootCause(e));
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.allocateException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}
// Identify if the request is Comet related now that the servlet has been allocated
boolean comet = false;
if (servlet instanceof CometProcessor
&& request.getAttribute("org.apache.tomcat.comet.support") == Boolean.TRUE) {
comet = true;
request.setComet(true);
}
// Acknowledge the request
try {
response.sendAcknowledgement();
} catch (IOException e) {
container.getLogger().warn(sm.getString("standardWrapper.acknowledgeException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.acknowledgeException",
wrapper.getName()), e);
throwable = e;
exception(request, response, e);
servlet = null;
}
MessageBytes requestPathMB = request.getRequestPathMB();
DispatcherType dispatcherType = DispatcherType.REQUEST;
if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
request.setAttribute
(ApplicationFilterFactory.DISPATCHER_TYPE_ATTR,
dispatcherType);
request.setAttribute
(ApplicationFilterFactory.DISPATCHER_REQUEST_PATH_ATTR,
requestPathMB);
// Create the filter chain for this request
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
// Reset comet flag value after creating the filter chain
request.setComet(false);
// Call the filter chain for this request
// NOTE: This also calls the servlet's service() method
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
//TODO SERVLET3 - async
((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
request.setComet(true);
} else {
filterChain.doFilter(request.getRequest(),
response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
//TODO SERVLET3 - async
((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
} else if (comet) {
request.setComet(true);
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
} catch (ClientAbortException e) {
throwable = e;
exception(request, response, e);
} catch (IOException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
} catch (UnavailableException e) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
// throwable = e;
// exception(request, response, e);
wrapper.unavailable(e);
long available = wrapper.getAvailable();
if ((available > 0L) && (available < Long.MAX_VALUE)) {
response.setDateHeader("Retry-After", available);
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
sm.getString("standardWrapper.isUnavailable",
wrapper.getName()));
} else if (available == Long.MAX_VALUE) {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
sm.getString("standardWrapper.notFound",
wrapper.getName()));
}
// Do not save exception in 'throwable', because we
// do not want to do exception(request, response, e) processing
} catch (ServletException e) {
Throwable rootCause = StandardWrapper.getRootCause(e);
if (!(rootCause instanceof ClientAbortException)) {
container.getLogger().error(sm.getString(
"standardWrapper.serviceExceptionRoot",
wrapper.getName(), context.getName(), e.getMessage()),
rootCause);
}
throwable = e;
exception(request, response, e);
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString(
"standardWrapper.serviceException", wrapper.getName(),
context.getName()), e);
throwable = e;
exception(request, response, e);
}
// Release the filter chain (if any) for this request
if (filterChain != null) {
if (request.isComet()) {
// If this is a Comet request, then the same chain will be used for the
// processing of all subsequent events.
filterChain.reuse();
} else {
filterChain.release();
}
}
// Deallocate the allocated servlet instance
try {
if (servlet != null) {
wrapper.deallocate(servlet);
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.deallocateException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
// If this servlet has been marked permanently unavailable,
// unload it and release this instance
try {
if ((servlet != null) &&
(wrapper.getAvailable() == Long.MAX_VALUE)) {
wrapper.unload();
}
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.unloadException",
wrapper.getName()), e);
if (throwable == null) {
throwable = e;
exception(request, response, e);
}
}
long t2=System.currentTimeMillis();
long time=t2-t1;
processingTime += time;
if( time > maxTime) maxTime=time;
if( time < minTime) minTime=time;
}
我们看到这里用了servlet = wrapper.allocate();步骤来生成servlet,然后调用了servlet的service方法去处理request和Response,至于后续的service转入doPost和doGet是比较好理解的,我们主要来看servlet是如何被生成的:
if (instance == null) {
synchronized (this) {
if (instance == null) {
try {
if (log.isDebugEnabled())
log.debug("Allocating non-STM instance");
instance = loadServlet();
// For non-STM, increment here to prevent a race
// condition with unload. Bug 43683, test case #3
if (!singleThreadModel) {
newInstance = true;
countAllocated.incrementAndGet();
}
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException
(sm.getString("standardWrapper.allocate"), e);
}
}
}
核心代码就是这样,我们看到servlet与其对应的wrapper是单例的,而wrapper是在项目启动时解析web.xml产生的,每个通过配置文件或者注解注册的servlet类只对应了一个wrapper,换句话说servlet的所有类都是单例,两次相同servlet处理的请求其实是由同一个servlet对象进行处理的,只不过运行在不同的线程中而已。因此千万不要再把数据设置为servlet的内部变量然后每次请求去初始化了,因为对于一个非线程安全的数据来说,如果两次同servlet的请求时间差足够短的话,这个数据源可能在第一次请求还没有结束的时候就被第二次请求给初始化了,由此产生的数据bug也是非常可怕的,因此对于请求处理过程的数据都在doPost或doGet方法内再进行定义,切记!
请求的处理过程大体就是这样,刚刚着重讲的是request,其实在这个流程中response应该也明白了,还记得一开始的outputBuffer被填装进入了第一个Response中吗,只要在适配过程中给第二个response传入这个输出流,那么response的print方法就非常容易实现了。
在后续的篇章中我们将继续研究Tomcat的很多细节,如servletContext啊、session啊,以及我们将会和J2EE的规范一起来研究整体的架构设计。