【Tomcat源碼分析】從零開始理解 HTTP 請求處理
前言
承接上文容器處理機(jī)制,當(dāng) postParseRequest方法返回真值時,容器將繼續(xù)處理請求。在 service 方法中,通過 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response) 這行代碼,容器將請求傳遞至管道的第一步,開啟后續(xù)的處理流程。
- Connector 調(diào)用 getService() 方法返回 StandardService 對象,
- StandardService 接著調(diào)用 getContainer() 方法返回 StandardEngine 對象,
- 最終 StandardEngine 調(diào)用 getPipeline() 方法返回與其關(guān)聯(lián)的 StandardPipeline 對象。
Engine 處理請求
回顧前文,StandardEngine 的構(gòu)造函數(shù)中,為其關(guān)聯(lián)的 Pipeline 添加了名為 StandardEngineValve 的基本閥,代碼如下:
public StandardEngine() {
super();
pipeline.setBasic(new StandardEngineValve());
try {
setJvmRoute(System.getProperty("jvmRoute"));
} catch(Exception ex) {
log.warn(sm.getString("standardEngine.jvmRouteFail"));
}
}
接下來,我們深入探究 StandardEngineValve 的 invoke() 方法。該方法主要負(fù)責(zé)選擇合適的 Host,并調(diào)用該 Host 所關(guān)聯(lián)的 Pipeline 中第一個 Valve 的 invoke() 方法,將請求傳遞至下一處理階段。
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Select the Host to be used for this Request
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());
}
// Ask this Host to process this request
host.getPipeline().getFirst().invoke(request, response);
}
StandardEngineValve 的 invoke() 方法邏輯清晰,首先檢查當(dāng)前 Engine 容器是否包含 Host 容器,若不存在,則返回 400 錯誤。若存在,則繼續(xù)執(zhí)行 host.getPipeline().getFirst().invoke(request, response)??梢钥吹剑琀ost 容器先獲取其關(guān)聯(lián)的 Pipeline,再獲取 Pipeline 中的第一個 Valve,并調(diào)用該 Valve 的 invoke() 方法,將請求傳遞至下一個處理階段。
Host 處理請求
分析 Host 時,我們從其構(gòu)造函數(shù)入手,該方法主要負(fù)責(zé)設(shè)置基礎(chǔ)閥門。
public StandardHost() {
super();
pipeline.setBasic(new StandardHostValve());
}
StandardPipeline 調(diào)用 getFirst 方法獲取第一個 Valve 來處理請求。由于基本閥通常是最后一個添加的 Valve,因此最終請求會由基本閥進(jìn)行處理。
StandardHost 的 Pipeline 中必然包含 ErrorReportValve 和 StandardHostValve 兩個 Valve。ErrorReportValve 主要負(fù)責(zé)檢測 HTTP 請求過程中是否出現(xiàn)過異常,如果有異常,則直接拼裝 HTML 頁面,并輸出到客戶端。
接下來,我們仔細(xì)觀察 ErrorReportValve 的 invoke() 方法:
public void invoke(Request request, Response response)
throws IOException, ServletException {
// Perform the request
// 1. 先將 請求轉(zhuǎn)發(fā)給下一個 Valve
getNext().invoke(request, response);
// 2. 這里的 isCommitted 表明, 請求是正常處理結(jié)束
if (response.isCommitted()) {
return;
}
// 3. 判斷請求過程中是否有異常發(fā)生
Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (request.isAsyncStarted() && ((response.getStatus() < 400 &&
throwable == null) || request.isAsyncDispatching())) {
return;
}
if (throwable != null) {
// The response is an error
response.setError();
// Reset the response (if possible)
try {
// 4. 重置 response 里面的數(shù)據(jù)(此時 Response 里面可能有些數(shù)據(jù))
response.reset();
} catch (IllegalStateException e) {
// Ignore
}
// 5. 這就是我們??吹降?500 錯誤碼
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
response.setSuspended(false);
try {
// 6. 這里就是將 異常的堆棧信息組合成 html 頁面, 輸出到前臺
report(request, response, throwable);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
if (request.isAsyncStarted()) {
// 7. 若是異步請求的話, 設(shè)置對應(yīng)的 complete (對應(yīng)的是 異步 Servlet)
request.getAsyncContext().complete();
}
}
ErrorReportValve 的 invoke() 方法首先執(zhí)行下一個 Valve 的 invoke() 方法,然后根據(jù)返回的 Request 屬性設(shè)置一些錯誤信息。那么下一個 Valve 是誰呢?實(shí)際上就是基本閥門 StandardHostValve。
接下來,我們深入分析 StandardHostValve 的 invoke() 方法是如何實(shí)現(xiàn)的:
@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());
}
// Don't fire listeners during async processing
// If a request init listener throws an exception, the request is
// aborted
boolean asyncAtStart = request.isAsync();
// An async error page may dispatch to another resource. This flag helps
// ensure an infinite error handling loop is not entered
boolean errorAtStart = response.isError();
if (asyncAtStart || context.fireRequestInitEvent(request)) {
// Ask this Context to process this request
try {
context.getPipeline().getFirst().invoke(request, response);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (errorAtStart) {
container.getLogger().error("Exception Processing " +
request.getRequestURI(), t);
} else {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
throwable(request, response, t);
}
}
// If the request was async at the start and an error occurred then
// the async error handling will kick-in and that will fire the
// request destroyed event *after* the error handling has taken
// place
if (!(request.isAsync() || (asyncAtStart &&
request.getAttribute(
RequestDispatcher.ERROR_EXCEPTION) != null))) {
// Protect against NPEs if context was destroyed during a
// long running request.
if (context.getState().isAvailable()) {
if (!errorAtStart) {
// 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);
}
}
context.fireRequestDestroyEvent(request);
}
}
}
// Access a session (if present) to update last accessed time, based on a
// strict interpretation of the specification
if (ACCESS_SESSION) {
request.getSession(false);
}
// 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());
}
}
StandardHostValve 的 invoke() 方法首先校驗(yàn) Request 是否存在 Context,這個 Context 在執(zhí)行 CoyoteAdapter.postParseRequest 方法時就已經(jīng)設(shè)置好了。如果 Context 不存在,則返回 500 錯誤。接下來,依然是熟悉的流程:context.getPipeline().getFirst().invoke,該管道獲取的是基礎(chǔ)閥門 StandardContextValve,我們繼續(xù)關(guān)注它的 invoke() 方法。
Context 處理請求
接著,Context 會去處理請求,同樣地,StandardContextValve 的 invoke() 方法會被調(diào)用:
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// Disallow any direct access to resources under WEB-INF or META-INF
MessageBytes requestPathMB = 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;
}
// Select the Wrapper to be used for this Request
Wrapper wrapper = request.getWrapper();
if (wrapper == null || wrapper.isUnavailable()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// Acknowledge the request
try {
response.sendAcknowledgement();
} catch (IOException ioe) {
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);
}
Wrapper 處理請求
Wrapper 是一個 Servlet 的包裝,我們先來了解一下它的構(gòu)造方法。其主要作用是設(shè)置基礎(chǔ)閥門 StandardWrapperValve。
接下來,我們來分析 StandardWrapperValve 的 invoke() 方法。
@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.incrementAndGet();
StandardWrapper wrapper = (StandardWrapper) getContainer();
Servlet servlet = null;
Context context = (Context) wrapper.getParent();
// Check for the application being marked unavailable
if (!context.getState().isAvailable()) {
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 {
// 關(guān)鍵點(diǎn)1:這兒調(diào)用Wrapper的allocate()方法分配一個Servlet實(shí)例
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;
}
MessageBytes requestPathMB = request.getRequestPathMB();
DispatcherType dispatcherType = 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);
// Create the filter chain for this request
// 關(guān)鍵點(diǎn)2,創(chuàng)建過濾器鏈,類似于Pipeline的功能
ApplicationFilterChain filterChain =
ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// 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()) {
request.getAsyncContextInternal().doInternalDispatch();
} else {
// 關(guān)鍵點(diǎn)3,調(diào)用過濾器鏈的doFilter,最終會調(diào)用到Servlet的service方法
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 {
// 關(guān)鍵點(diǎn)3,調(diào)用過濾器鏈的doFilter,最終會調(diào)用到Servlet的service方法
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
// 關(guān)鍵點(diǎn)4,釋放掉過濾器鏈及其相關(guān)資源
if (filterChain != null) {
filterChain.release();
}
// 關(guān)鍵點(diǎn)5,釋放掉Servlet及相關(guān)資源
// 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
// 關(guān)鍵點(diǎn)6,如果servlet被標(biāo)記為永遠(yuǎn)不可達(dá),則需要卸載掉它,并釋放這個servlet實(shí)例
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;
}