tomcat請求處理分析(六)servlet的處理過程
1.1.1.1 servlet的解析過程
servlet的解析分為兩步實現,第一個是匹配到對應的Wrapper,第二個是載入對應的servlet並進行資料,這些資料是怎麼到介面的,response.getWrite()獲取對應的流,然後寫入這個流中,這個流中就有上文的outputBuffer。
匹配到對應Wrapper
在上文中我們曾經走到過了doRun方法,現在就直接從這裡開始
執行順序如下:
NioEndpoint(run)==>下步呼叫doRun
NioEndpoint(doRun)==>下步呼叫state = handler.process(ka,status);
handler例項物件Http11ConnectionHandler其繼承AbstractConnectionHandler
AbstractConnectionHandler(process) ==》下步呼叫 state = processor.process(wrapper);
processor例項物件Http11NioProcessor 其繼承AbstractHttp11Processor
AbstractHttp11Processor(process) ==》下步呼叫getAdapter().service(request, response);
CoyoteAdapter.service(request,response)這個方法就已經接近核心處理了,程式碼如下:
在第一處標紅的地方,對請求進行了解析,並且匹配到對應的主機和context和wrapper
在第二處標紅的地方是載入servlet並進行呼叫處理
在第三處標紅的地方是重新整理流,響應到介面
@SuppressWarnings("deprecation") @Override publicvoid service(org.apache.coyote.Request req, org.apache.coyote.Responseres) throws Exception { Request request = (Request)req.getNote(ADAPTER_NOTES); Response response =(Response) res.getNote(ADAPTER_NOTES); if (request == null) { //建立一個request物件 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; boolean postParseSuccess = false; try { //設定執行執行緒執行緒名 req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get()); //對uri進行解碼,主要是解析報文,如果不合法返回響應碼400 postParseSuccess = postParseRequest(req,request, res, response); if (postParseSuccess) { //設定是否支援非同步 request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported()); //不斷呼叫管道載入對應的servlet進行呼叫,其中傳遞了response引數,所以可以放入流資料 connector.getService().getContainer().getPipeline().getFirst().invoke(request,response); if (request.isComet()) { if (!response.isClosed()&& !response.isError()) { comet = true; res.action(ActionCode.COMET_BEGIN, null); if (request.getAvailable()|| (request.getContentLength() >0 &&(!request.isParametersParsed()))) { event(req, res,SocketStatus.OPEN_READ); } } else { request.setFilterChain(null); } } } //如果是非同步請求 if (request.isAsync()){ async = true; ReadListener readListener= req.getReadListener(); if (readListener != null&&request.isFinished()) { ClassLoader oldCL =null; try { oldCL =request.getContext().bind(false, null); if (req.sendAllDataReadEvent()){ req.getReadListener().onAllDataRead(); } } finally { request.getContext().unbind(false,oldCL); } } Throwable throwable = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); if (!request.isAsyncCompleting()&& throwable !=null) { request.getAsyncContextInternal().setErrorState(throwable, true); } } else if (!comet) { //如果為同步請求,Flush並關閉輸入輸出流 request.finishRequest(); response.finishResponse(); } } catch (IOExceptione) { // Ignore } finally{ AtomicBoolean error = new AtomicBoolean(false); res.action(ActionCode.IS_ERROR,error); if (request.isAsyncCompleting()&& error.get()) { res.action(ActionCode.ASYNC_POST_PROCESS, null); async = false; } if (!async&& !comet) { if (postParseSuccess){ request.getMappingData().context.logAccess( request, response, System.currentTimeMillis()- req.getStartTime(), false); } } req.getRequestProcessor().setWorkerThreadName(null); if (!comet &&!async) { request.recycle(); response.recycle(); } else{ request.clearEncoders(); response.clearEncoders(); } } }
1.1.1.1.1 構造對應request的MappingData屬性
postParseRequest:CoyoteAdapter(org.apache.catalina.connector)
connector.getService().getMapper().map(serverName,decodedURI,version, request.getMappingData());
下面是request部分構造程式碼
protected final MappingDatamappingData =new MappingData();
public MappingDatagetMappingData() {
return mappingData;
}
根據呼叫方法,我們可以知道其傳入的引數有request例項成員物件mappingData的引用型別,所以下面的匹配的Context以及Wrapper所到的mappingData都是當前request的屬性
============================================
map: Mapper (org.apache.catalina.mapper)
internalMap(host.getCharChunk(),uri.getCharChunk(), version, mappingData);
internalMap:758,Mapper (org.apache.catalina.mapper)
在這裡面匹配到了對應的虛擬主機,存放到了mappingData中去,以及Context主要採用了二分查詢獲取遊標進行匹配
然後其呼叫internalMapWrapper(contextVersion,uri, mappingData);匹配到了Wrapper存放到mappingData,其匹配規則如下
/**
* a: 對全新的路徑進行精準匹配
* b: 對全新的路徑進行萬用字元匹配
* c: 根據全新的路徑,進行查詢是否存在相應的檔案,如果存在相應的檔案,則需要將該檔案返回。在回前我們需要進一步確認,這個檔案是不是講檔案內容原始碼返回,還是像jsp檔案一樣,進行一定的處理然後再返回,所以又要確認下檔案的副檔名是怎樣的
* c1: 嘗試尋找能夠處理該副檔名的servlet,即進行副檔名匹配,如果找到,則使用對應的servlet
* c2: 如果沒找到,則預設使用defaultWrapper,即DefaultServlet(它只會將檔案內容原始碼返回,不做任何處理)
* d: 對全新的路徑進行副檔名匹配(與c的目的不同,c的主要目的是想返回一個檔案的內容,在返回內容前涉及到副檔名匹配,所以4c的前提是存在對應路徑的檔案)
* 案例1: a.html,a、b沒有匹配到,到c的時候,找到了該檔案,然後又嘗試副檔名匹配,來決定是走c1還是c2,由於.html還沒有對應的servlet來處理,就使用了預設的DefaultServlet
* 案例2: a.jsp,同上,在走到c的時候,找到了處理.jsp對應的servlet,所以走了c1
* 案例3: a.action,如果根目錄下有a.action檔案,則走到c1的時候,進行副檔名匹配,匹配到了SecondServlet,即走了c1,使用SecondServlet來處理請求;如果根目錄下沒有a.action檔案,則走到了d,進行副檔名匹配,同樣匹配到了SecondServlet,即走了d,同樣使用SecondServlet來處理請求
* 案例4: first/abc,執行b的時候,就匹配到了FirstServlet,所以使用FirstServlet來處理請求
* */
private final void internalMapWrapper(ContextVersioncontextVersion,
CharChunkpath,
MappingDatamappingData)throws IOException {
。。。。。。。
if (found) {
mappingData.wrapperPath.setString(wrappers[pos].name);
if (path.getLength() >length) {
mappingData.pathInfo.setChars
(path.getBuffer(),
path.getOffset()+ length,
path.getLength()- length);
}
mappingData.requestPath.setChars
(path.getBuffer(), path.getOffset(),path.getLength());
mappingData.wrapper=wrappers[pos].object;
mappingData.jspWildCard=wrappers[pos].jspWildCard;
}
}
}
1.1.1.1.2 載入servlet並呼叫
一起執行順序來看一下一個servlet如何進行載入
invoke:98,StandardEngineValve (org.apache.catalina.core)
程式碼如下:
/**
* 基於請求的服務名選擇合適的虛擬主機進行請求處理
*
* 如果不能匹配到對應主機,返回對應的http錯誤
*
* @param request 執行請求
* @param response Response tobe produced
*
*/
@Override
publicfinal void invoke(Request request,Response response)
throws IOException,ServletException{
//根據請求找到對應的host
Host host =request.getHost();
if (host == null) {
response.sendError
(HttpServletResponse.SC_BAD_REQUEST,
sm.getString("standardEngine.noHost",
request.getServerName()));
return;
}
//設定當前請求是否支援非同步
if (request.isAsyncSupported()){
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
//org.apache.catalina.valves.AccessLogValve[localhost]
//org.apache.catalina.valves.ErrorReportValve[localhost]
//org.apache.catalina.core.StandardHostValve[localhost]
/**
* 呼叫host的第一個valve
*
* 其執行原理是獲取根據管道獲取第一個閥門AccessLogValve呼叫其invoke方法
*
* AccessLogValve的invoke第一行呼叫getNext().invoke()呼叫了ErrorReportValve
*
* 同理呼叫了StandardHostValve的invoke方法所以實際呼叫的最先的是invoke方法
* */
host.getPipeline().getFirst().invoke(request,response);
}
invoke:143,StandardHostValve (org.apache.catalina.core)
下步呼叫context.getPipeline().getFirst().invoke(request,response);
invoke:504,AuthenticatorBase(org.apache.catalina.authenticator)
下步呼叫getNext().invoke(request, response);
/**
*
* 基於URI的request獲取對應的Wrapper如果沒有匹配到返回一個HTTP錯誤
*
* 在這個方法中做的事情主要是獲取wrapper然後進行對應管道的閥門進行呼叫
* @param request Request tobe processed
* @param response Response tobe produced
*
* @exception IOException if aninput/output error occurred
* @exception ServletException ifa servlet error occurred
*/
@Override
publicfinal void invoke(Request request,Response response)
throws IOException,ServletException{
// Disallow any directaccess to resources under WEB-INF or META-INF
MessageBytesrequestPathMB = request.getRequestPathMB();
if ((requestPathMB.startsWithIgnoreCase("/META-INF/",0))
||(requestPathMB.equalsIgnoreCase("/META-INF"))
||(requestPathMB.startsWithIgnoreCase("/WEB-INF/",0))
||(requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
//獲取當前request利用的wrapper
Wrapper wrapper =request.getWrapper();
if (wrapper == null||wrapper.isUnavailable()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Acknowledge the request
try {
response.sendAcknowledgement();
} catch(IOExceptionioe) {
container.getLogger().error(sm.getString(
"standardContextValve.acknowledgeException"),ioe);
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ioe);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
if (request.isAsyncSupported()){
request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
}
wrapper.getPipeline().getFirst().invoke(request,response);
}
invoke:98,StandardWrapperValve (org.apache.catalina.core)
其主要操作如下:獲取到對應的StandardWrapper,然後分配一個servlet,具體在loadServlet中進行例項話,再分配由於是成員變數,只有第一次呼叫的時候才會進行分配,之後直接返回第一次的例項化對一下物件,具體看allocate方法
public final void invoke(Request request,Responseresponse)
throws IOException,ServletException{
//獲取到對應的StandardWrapper
StandardWrapper wrapper= (StandardWrapper) getContainer();
//每個請求開始servlet都是為空
Servlet servlet = null;
Context context =(Context) wrapper.getParent();
try {
if (!unavailable){
servlet = wrapper.allocate();
}
}
MessageBytes requestPathMB =request.getRequestPathMB();
DispatcherTypedispatcherType = DispatcherType.REQUEST;
if (request.getDispatcherType()==DispatcherType.ASYNC)dispatcherType = DispatcherType.ASYNC;
request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
requestPathMB);
// 建立過濾器例項
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request,wrapper, servlet);
if ((servlet !=null) &&(filterChain !=null)) {
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()){
request.getAsyncContextInternal().doInternalDispatch();
} else if(comet) {
filterChain.doFilterEvent(request.getEvent());
} 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()){
request.getAsyncContextInternal().doInternalDispatch();
} else if(comet) {
filterChain.doFilterEvent(request.getEvent());
} else{
filterChain.doFilter
(request.getRequest(), response.getResponse());
}
}
}
}
//釋放過濾器
if (filterChain!=null) {
if (request.isComet()){
// If this is a Cometrequest, then the same chain will be used for the
// processing of allsubsequent events.
filterChain.reuse();
} else{
filterChain.release();
}
}
// Deallocate theallocated servlet instance
try {
if (servlet !=null) {
wrapper.deallocate(servlet);
}
} catch (Throwablee) {
ExceptionUtils.handleThrowable(e);
container.getLogger().error(sm.getString("standardWrapper.deallocateException",
wrapper.getName()),e);
if (throwable == null) {
throwable = e;
exception(request,response, e);
}
}
try {
if ((servlet !=null) &&
(wrapper.getAvailable() ==Long.MAX_VALUE)) {
wrapper.unload();
}
} catch (Throwablee) {
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的呼叫
按照這個順序執行完所有過濾器就會執行對應的servlet,這是因為在建立過濾器
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request,wrapper, servlet); 的時候,將servlet給注入進去了,當過濾器執行完了,會執行呼叫servlet的service, 由於自己寫的servlet是會繼承HttpServlet的,所以將呼叫其service方法
呼叫如下:
internalDoFilter:,ApplicationFilterChain
方法如下:下面展示了兩個service ,同在HttpServlet只是方法的引數有所不同,載入過程先呼叫一個,然後第一個再呼叫第二個,根據請求方法呼叫自己對應的Servlet中的doGet等一些列方法
protected void service(HttpServletRequest req,HttpServletResponseresp)
throws ServletException,IOException{
//獲取對應的方法
String method = req.getMethod();
/**
* 根據請求method呼叫對應方法
* GET ==>doGet(req, resp)
* head ==> doHead(req, resp)
* post ==>doPost(req,resp)
*
* */
if (method.equals(METHOD_GET)) {
long lastModified= getLastModified(req);
if (lastModified == -1) {
// servlet doesn't supportif-modified-since, no reason
// to go through furtherexpensive logic
doGet(req,resp);
} else{
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch(IllegalArgumentExceptioniae) {
// Invalid date header -proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince< (lastModified /1000 * 1000)) {
// If the servlet mod timeis later, call doGet()
// Round down to thenearest second for a proper compare
// A ifModifiedSince of-1 will always be less
maybeSetLastModified(resp,lastModified);
doGet(req,resp);
} else{
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified =getLastModified(req);
maybeSetLastModified(resp,lastModified);
doHead(req,resp);
} else if(method.equals(METHOD_POST)) {
doPost(req, resp);
} else if(method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if(method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if(method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if(method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NOservlet supports whatever
// method was requested, anywhereon this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = newObject[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg,errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,errMsg);
}
}
上面已經講述了一個servlet呼叫的過程,他的資訊是如何返回掉流中,我們的看一下response,getWrite方法
可以看出這個流最終將outputBuffer給封裝,其write方法
所以是寫到上文封裝的流中,最後一併解析到頁面,可以參照請求到響應流。