前面分析了tomcat啟動,見
Tomcat啟動源代碼分析。啟動分析的后面已經涉及到了對客戶端連接進來的socket的處理的,那么今天的文章就沿著上面的文章寫下去吧。
一、Connector處理請求
MasterSlaveWorkerThread調用PoolTcpEndpoint進行處理:
// Process the request from this socket
endpoint.processSocket(socket, con, threadData);
thereaDate是一個對象數組Object[] threadData,con是TcpConnection con = new TcpConnection()。
PoolTcpEndpoint進行處理:
void processSocket(Socket s, TcpConnection con, Object[] threadData) {
// Process the connection
int step = 1;
try {
// 1: Set socket options: timeout, linger, etc
setSocketOptions(s);
// 2: SSL handshake
step = 2;
if (getServerSocketFactory() != null) {
getServerSocketFactory().handshake(s);
}
// 3: Process the connection
step = 3;
con.setEndpoint(this);
con.setSocket(s);
getConnectionHandler().processConnection(con, threadData);
} catch (SocketException se) {
log.debug(sm.getString("endpoint.err.socket", s.getInetAddress()),
se);
// Try to close the socket
try {
s.close();
} catch (IOException e) {
}
} catch (Throwable t) {
if (step == 2) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("endpoint.err.handshake"), t);
}
} else {
log.error(sm.getString("endpoint.err.unexpected"), t);
}
// Try to close the socket
try {
s.close();
} catch (IOException e) {
}
} finally {
if (con != null) {
con.recycle();
}
}
}
最后交由connectionHandler即Http11ConnectionHandler進行處理:
public void processConnection(TcpConnection connection,
Object thData[]) {
Socket socket=null;
Http11Processor processor=null;
try {
processor=(Http11Processor)thData[Http11BaseProtocol.THREAD_DATA_PROCESSOR];
if (processor instanceof ActionHook) {
((ActionHook) processor).action(ActionCode.ACTION_START, null);
}
socket=connection.getSocket();
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
if( proto.secure ) {
SSLSupport sslSupport=null;
if(proto.sslImplementation != null)
sslSupport = proto.sslImplementation.getSSLSupport(socket);
processor.setSSLSupport(sslSupport);
} else {
processor.setSSLSupport( null );
}
processor.setSocket( socket );
processor.process(in, out);
// If unread input arrives after the shutdownInput() call
// below and before or during the socket.close(), an error
// may be reported to the client. To help troubleshoot this
// type of error, provide a configurable delay to give the
// unread input time to arrive so it can be successfully read
// and discarded by shutdownInput().
if( proto.socketCloseDelay >= 0 ) {
try {
Thread.sleep(proto.socketCloseDelay);
} catch (InterruptedException ie) { /* ignore */ }
}
TcpConnection.shutdownInput( socket );
} catch(java.net.SocketException e) {
// SocketExceptions are normal
Http11BaseProtocol.log.debug
(sm.getString
("http11protocol.proto.socketexception.debug"), e);
} catch (IOException e) {
// IOExceptions are normal
Http11BaseProtocol.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) {
// 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.
Http11BaseProtocol.log.error
(sm.getString("http11protocol.proto.error"), e);
} finally {
// if(proto.adapter != null) proto.adapter.recycle();
// processor.recycle();
if (processor instanceof ActionHook) {
((ActionHook) processor).action(ActionCode.ACTION_STOP, null);
}
// recycle kernel sockets ASAP
try { if (socket != null) socket.close (); }
catch (IOException e) { /* ignore */ }
}
}
}
Http11Processor進行處理,這里是比較細節的東西:選取重要的代碼觀摩一下:
inputBuffer.parseRequestLine();
inputBuffer.parseHeaders();
adapter.service(request, response);
上面第一條就是處理請求行,一般如下:
POST /loan/control/customer HTTP/1.0
獲取請求方法,請求的URI和協議名稱及版本。
第二條很明顯就是處理請求的報頭,詳見
Http 協議頭基礎。
最后是交給Adapter進行處理。這個Adapter是
CoyoteAdapter,CoyotConnector初始化的時候設置的。見上次文章中初始化CoyotConnector部分。
CoyoteAdapter的service方法:
/**
* Service method.
*/
public void service(Request req, Response res)
throws Exception {
CoyoteRequest request = (CoyoteRequest) req.getNote(ADAPTER_NOTES);
CoyoteResponse response = (CoyoteResponse) res.getNote(ADAPTER_NOTES);
if (request == null) {
// Create objects
request = (CoyoteRequest) connector.createRequest();
request.setCoyoteRequest(req);
response = (CoyoteResponse) 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());
}
try {
// Parse and set Catalina and configuration specific
// request parameters
postParseRequest(req, request, res, response);
// Calling the container
connector.getContainer().invoke(request, response);
response.finishResponse();
req.action( ActionCode.ACTION_POST_REQUEST , null);
} catch (IOException e) {
;
} catch (Throwable t) {
log(sm.getString("coyoteAdapter.service"), t);
} finally {
// Recycle the wrapper request and response
request.recycle();
response.recycle();
}
}
調用容器的invoke方法,這個容器就是StandardEngine?,F在連接器已經將request和response對象準備好,交由容器處理了。上面的處理主要是Connector處理,流程大致如下:
下面研究容器是怎么處理請求的。
二、容器處理請求
上面說到StandardEngine接收到請求,invoke方法調用。方法非常簡單:
public void invoke(Request request, Response response)
throws IOException, ServletException {
pipeline.invoke(request, response);
}
容器并不直接處理,而是交給一個pipeline的東東,這個pipeline里面會放置一些vavle,也就是說,請求沿著pipeline傳遞下去并且vavle對其進行相關的處理。這個valve有簡單的,也有復雜的,簡單的就是起個接力棒的作用,復雜的可以對請求做一些處理,比如說日志等,valve還可以自定義,查看server.xml配置文件就知道了。相關類圖如下:
具體可以看一下幾個Valve的功能:
StandardEngineValve:
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
// Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return; // NOTE - Not much else we can do generically
}
// Validate that any HTTP/1.1 request included a host header
HttpServletRequest hrequest = (HttpServletRequest) request;
if ("HTTP/1.1".equals(hrequest.getProtocol()) &&
(hrequest.getServerName() == null)) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHostHeader",
request.getRequest().getServerName()));
return;
}
// Select the Host to be used for this Request
StandardEngine engine = (StandardEngine) getContainer();
Host host = (Host) engine.map(request, true);
if (host == null) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getRequest().getServerName()));
return;
}
// Ask this Host to process this request
host.invoke(request, response);
}
這個valve驗證對象實例的正確性,選擇Host并交由其處理。
StandardHostValve:
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
// Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return; // NOTE - Not much else we can do generically
}
// Select the Context to be used for this Request
StandardHost host = (StandardHost) getContainer();
Context context = (Context) host.map(request, true);
if (context == null) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
sm.getString("standardHost.noContext"));
return;
}
// Bind the context CL to the current thread
Thread.currentThread().setContextClassLoader
(context.getLoader().getClassLoader());
// Update the session last access time for our session (if any)
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String sessionId = hreq.getRequestedSessionId();
if (sessionId != null) {
Manager manager = context.getManager();
if (manager != null) {
Session session = manager.findSession(sessionId);
if ((session != null) && session.isValid())
session.access();
}
}
// Ask this Context to process this request
context.invoke(request, response);
Thread.currentThread().setContextClassLoader
(StandardHostValve.class.getClassLoader());
}
Host的vavle也是先判斷對象實例,然后選擇相應的Context,查看是否有session信息并更新其最后獲取時間(tomcat的session研究放在后面,東西太多了),最后才調用context的invoke方法。StandardContext首先check context是否在reload,如果reload結束或者沒有reload那么就和上面幾個容器一樣,通過valve處理:
StandardContextValve:
public void invoke(Request request, Response response,
ValveContext valveContext)
throws IOException, ServletException {
// Validate the request and response object types
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
return; // NOTE - Not much else we can do generically
}
// Disallow any direct access to resources under WEB-INF or META-INF
HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
String contextPath = hreq.getContextPath();
String requestURI = ((HttpRequest) request).getDecodedRequestURI();
String relativeURI =
requestURI.substring(contextPath.length()).toUpperCase();
if (relativeURI.equals("/META-INF") ||
relativeURI.equals("/WEB-INF") ||
relativeURI.startsWith("/META-INF/") ||
relativeURI.startsWith("/WEB-INF/")) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
}
Context context = (Context) getContainer();
// Select the Wrapper to be used for this Request
Wrapper wrapper = null;
try {
wrapper = (Wrapper) context.map(request, true);
} catch (IllegalArgumentException e) {
badRequest(requestURI,
(HttpServletResponse) response.getResponse());
return;
}
if (wrapper == null) {
notFound(requestURI, (HttpServletResponse) response.getResponse());
return;
}
// Ask this Wrapper to process this Request
response.setContext(context);
wrapper.invoke(request, response);
}
常規檢查以后是安全請求檢查,然后得到一個wrapper,這個wrapper是啥呢?這就得StandardContextMapper,它的map方法大家可以研究一下,其實就是通過請求路徑查找相應class的過程,查找的優先級一目了然,首先是精確匹配,然后是前綴匹配,擴展匹配最后是默認匹配。Wrapper其實就是封裝了一個servlet。其valve類StandardWrapperValve的invoke方法有點長,大致功能如下:實例檢查,可獲得性檢查,獲取servlet實例,Filter和Servlet執行。這個時候就會調用servlet的service方法,交給我們自定義的servlet進行實際的業務處理。