52
深入淺出 Web 容器 Tomcat 原始碼分析 林信良 資深技術顧問 http://openhome.cc [email protected]

深入淺出 Web 容器 - Tomcat 原始碼分析

Embed Size (px)

Citation preview

深入淺出 Web 容器 Tomcat 原始碼分析

林信良

資深技術顧問

http://openhome.cc

[email protected]

主題

• Web 容器與 Servlet

• 從 HTTP 請求到 service()

•在service()的前後

• 從 JSP 到 Servlet

• 自訂標籤處理

Web 容器與 Servlet

Web 容器與 Servlet

容器(Container)…裝水的嗎?

別鬧了…容器是個 Java 應用程式,介於 Servlet 與Web 伺服器之間,我管很多事,你不用認識 Web 伺服器,你只要認得我!

Web 容器與 Servlet

管很多事?哪些事啊?

你沒想過 HTTP 那些麻煩的東西是怎麼變成 Java 物件的嗎?你以為 Servlet 中的

Request 與 Response 是怎麼來的?

Web 容器與 Servlet

看起來好像很複雜?

要了解我的內在確實不容易,那…先從簡單的開始好了…

知道一個Servlet是實作 Servlet 介面嗎? …..

來個簡化版的…

if(servlets.get(servletName) == null) {

servlet = (Servlet) myClass.newInstance();

servlets.put(servletName, servlet);

servlet.init(new Config(myClass));

}

else {

servlet = servlets.get(servletName);

}

servlet.service(

(ServletRequest) requestFacade,

(ServletResponse) responseFacade);

public void destroy() {

for(Servlet servlet : servlets.values()) {

servlet.destroy();

}

}

至少你要知道init()、service()與destroy()…

RequestFacade requestFacade =

new RequestFacade(request);

ResponseFacade responseFacade =

new ResponseFacade(response);

Web 容器與 Servlet

你管的就是這些?

當然更多!不只ServletRequest、ServletResponse、ServletConfig、Servlet這些…

對了!這個範例改寫自這邊…

http://onjava.com/pub/a/onjava/2003/05/14/j

ava_webserver.html

從 HTTP 請求到 service()

我實際上很強壯的啦….XD

從 HTTP 請求到 service()

當請求來到時是 Worker Thread 模式

public void run() {

while (running) {

// Allocate a new worker thread

MasterSlaveWorkerThread workerThread = createWorkerThread();

// Accept the next incoming connection from the server socket

...

Socket socket = acceptSocket();

workerThread.assign(socket);

}

..

}

沒原始碼沒真相...XD

採用Thread Pool...XD

從 HTTP 請求到 service()

接下來快轉一下…來到了Http11Processor…

我應該說過我負責建立Request與Response物件吧…這個類別剖析HTTP

並設定Request、Response

從 HTTP 請求到 service()

在Http11Processor的process()中,呼叫 adapter.service()

adapter.service(request, response);

再來的話這邊的request、response會被org.apache.catalina.connector套件中的Request、Response包裹起來...

WHY?

從 HTTP 請求到 service() package org.apache.catalina.connector;

public class Request implements HttpServletRequest {

}

package org.apache.catalina.connector;

public class Response implements HttpServletResponse {

}

從 HTTP 請求到 service() 再快轉一下…Request與Response送到容器ContainerBase,最後來到StandardWrapper…

你要注意一下loadServlet()方法…

回憶…

•Servlet第一次被請求時會被載入…

•建立ServletConfig…

•執行init()…

•呼叫service()…

public class StandardWrapper extends ContainerBase

implements ServletConfig, Wrapper, NotificationEmitter {

if (classLoader != null) {

classClass = classLoader.loadClass(actualClass);

} else {

classClass = Class.forName(actualClass);

}

}

...

// Instantiate and initialize an instance of the servlet class

try {

servlet = (Servlet) classClass.newInstance();

}

...

try {

servlet.init(facade);

servlet.service(req, res);

}

facade 參考至實作 ServletConfig 的實例

protected StandardWrapperFacade facade =

new StandardWrapperFacade(this);

從 HTTP 請求到 service()

SingleThreadModel 怎麼實現?你可以自己找找看…

喵…好複雜…Orz

已經省略了很多細節了…你可以用這個流程來研究一些更深入的…XD

在service()的前後

在service()的前後

你知道在Servlet前可以套用Filter

吧!…

好像是有這麼一回事…XD

實現了Interceptor Filter模式

當我們又來到Servlet…

在service()的前後

• GenericServlet類別

–還實作了ServletConfig介面,將容器呼叫init()方法時所傳入的ServletConfig實例封裝起來

– service()方法直接標示為abstract而沒有

任何的實作

• HTTP相關服務流程定義在HttpServlet的service()方法

在service()的前後

protected void service(HttpServletRequest req,

HttpServletResponse resp)

throws ServletException, IOException {

String method = req.getMethod(); // 取得請求的方法

if (method.equals(METHOD_GET)) { // HTTP GET

// 略...

doGet(req, resp);

// 略 ...

} else if (method.equals(METHOD_HEAD)) { // HTTP HEAD

// 略 ...

doHead(req, resp);

} else if (method.equals(METHOD_POST)) { // HTTP POST

// 略 ...

doPost(req, resp);

} else if (method.equals(METHOD_PUT)) { // HTTP PUT

// 略 ...

}

protected void doGet(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

String protocol = req.getProtocol();

String msg = lStrings.getString("http.method_get_not_supported");

if (protocol.endsWith("1.1")) {

resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);

} else {

resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);

}

}

在service()的前後

這實現了Template Method模式。。。

現在好像談過了幾個模式?Worker

Thread、Interceptor Filter、Template

Method…

在service()的前後

來談一下Session怎麼建好了…知道Session預設用Cookie儲存Session Id

吧…

餅乾我知道…

在service()的前後 if (connector.getEmptySessionPath()

&& isRequestedSessionIdFromCookie()) {

session = manager.createSession(getRequestedSessionId());

} else {

session = manager.createSession(null);

}

public Session createSession(String sessionId) {

Session session = createEmptySession()

session.setNew(true);

session.setValid(true);

session.setCreationTime(System.currentTimeMillis());

session.setMaxInactiveInterval(this.maxInactiveInterval);

if (sessionId == null) {

sessionId = generateSessionId();

session.setId(sessionId);

sessionCounter++;

return (session);

}

在service()的前後

你應該看一下org.apache.catalina.

session.StandardSession,了解Session怎麼儲存…好吧!接下來換個口味好了…

Servlet 談好久了…要來談一下 JSP 嗎? …XD

從 JSP 到 Servlet

跟你下棋的其實是一隻貓…@.@

從 JSP 到 Servlet

在我的世界中真正服務的只有Servlet…沒有JSP…

使用JSP不是比較簡單嗎? …

從 JSP 到 Servlet

<%@page contentType="text/html" pageEncoding="UTF-8"%>

<html>

<head>

<title>SimpleJSP</title>

</head>

<body>

<h1><%= new java.util.Date() %></h1>

</body>

</html>

從 JSP 到 Servlet package org.apache.jsp;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class index_jsp

extends org.apache.jasper.runtime.HttpJspBase

implements org.apache.jasper.runtime.JspSourceDependent {

// 略...

public void _jspInit() {

// 略...

}

public void _jspDestroy() {

}

public void _jspService(HttpServletRequest request,

HttpServletResponse response)

throws java.io.IOException, ServletException {

// 略...

}

}

從 JSP 到 Servlet

從 JSP 到 Servlet

public abstract class HttpJspBase extends HttpServlet

implements HttpJspPage {

// 略...

public final void init(ServletConfig config)

throws ServletException {

super.init(config);

jspInit();

_jspInit();

}

// 略...

public final void destroy() {

jspDestroy();

_jspDestroy();

}

public final void service(HttpServletRequest request,

HttpServletResponse response)

throws ServletException, IOException {

_jspService(request, response);

}

// 略...

}

從 JSP 到 Servlet

•在<%!與%>之間宣告的程式碼,都將轉譯為Servlet中的類別成員或方法

• <%與%>之間所包括的內容,將被轉譯為Servlet原始碼_jspService()方法中的內容

• <%=與%>運算式元素中的運算式,會直接轉譯為out物件print()輸出時的指定內容

從 JSP 到 Servlet

• 隱含物件,其實就是_jspService()中的

區域參考名稱…

application = pageContext.getServletContext();

config = pageContext.getServletConfig();

session = pageContext.getSession();

out = pageContext.getOut();

原則上是…因為都是轉成Servlet…只不過角色

職責不同…這是另一個故事了…

這麼說Servlet作的到的,JSP

都作的到 …

自訂標籤處理

我討厭義大利麵...>.<

自訂標籤處理

別傻了...我只處理 Java 的東西…每個標籤

後面都還是 Java 物件在處理事情。。XD

聽說使用 JSTL 就不用吃義大利麵 …

那是新的 HTML 標籤嗎?。。XD

自訂標籤處理

自訂標籤處理

• 當JSP網頁中包括Simple Tag自訂標籤 ,在轉譯之後... 1. 建立自訂標籤處理器實例。

2. 呼叫標籤處理器的setJspContext()方法設定PageContext實例。

3. 如果是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的setParent()方法,並傳入外層標籤處理器的實例。

4. 設定標籤處理器屬性(例如這邊是呼叫IfTag的setTest()方法來設定)。

5. 呼叫標籤處理器的setJspBody()方法設定JspFragment實例。

6. 呼叫標籤處理器的doTag()方法。

7. 銷毀標籤處理器實例。

cc.openhome.WhenTag _jspx_th_f_005fwhen_005f0 =

new cc.openhome.WhenTag();

...

_jspx_th_f_005fwhen_005f0.setJspContext(_jspx_page_context);

_jspx_th_f_005fwhen_005f0.setParent(_jspx_parent);

_jspx_th_f_005fwhen_005f0.setTest(…);

_jspx_th_f_005fwhen_005f0.setJspBody(new Helper(…));

_jspx_th_f_005fwhen_005f0.doTag();

<f:choose>

<f:when test="${user.valid}">

<h1>${user.name}登入成功</h1>

</f:when>

..

</f:choose>

這代表用Simple Tag實作標籤時,你的標籤處理器不能太肥大…

自訂標籤處理

• 當JSP中遇到TagSupport自訂標籤時

1. 嘗試從標籤池(Tag Pool)找到可用的標籤物件,如果找到就直接使用,如果沒找到就建立新的標籤物件。

2. 呼叫標籤處理器的setPageContext()方法設定PageContext實例。

3. 如果是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的setParent()方法,並傳入外層標籤處理器的實例。

4. 設定標籤處理器屬性(例如這邊是呼叫IfTag的setTest()方法來設定)。

5. 呼叫標籤處理器的doStartTag()方法,並依不同的傳回值決定是否執行本體或呼叫doAfterBody()、doEndTag()方法。

6. 將標籤處理器實例置入標籤池中以便再度使用。

org.apache.taglibs.standard.tag.rt.core.WhenTag

_jspx_th_c_005fwhen_005f0 =

(org.apache.taglibs.standard.tag.rt.core.WhenTag)

_005fjspx_005ftagPool_005fc_005fwhen_0026_005ftest.get(

org.apache.taglibs.standard.tag.rt.core.WhenTag.class);

...

_005fjspx_005ftagPool_005fc_005fwhen_0026_005ftest.reuse(

_jspx_th_c_005fwhen_005f0);

這代表用TagSupport實作標籤時,必須注意是否要重置標籤處理器的狀態。。

Pool的工作是由org.apache.jasper.

runtime.TagHandlerPool完成…

自訂標籤處理

自訂標籤處理 public Tag get(Class handlerClass) throws JspException {

Tag handler = null;

synchronized( this ) {

if (current >= 0) {

handler = handlers[current--];

return handler;

}

}

try {

Tag instance = (Tag) handlerClass.newInstance();

}

public void reuse(Tag handler) {

synchronized( this ) {

if (current < (handlers.length - 1)) {

handlers[++current] = handler;

return;

}

}

}

自訂標籤處理

是啦!不過…Tag File 會被我轉成 Simple

Tag 的實作…XD

我記得還有個 Tag File 的東西…

package org.apache.jsp.tag.web;

import javax.servlet.*;

import javax.servlet.http.*;

import javax.servlet.jsp.*;

public final class Errors_tag

extends javax.servlet.jsp.tagext.SimpleTagSupport

implements org.apache.jasper.runtime.JspSourceDependent {

最後…

因為我代勞太多事了…你以為有些行為很奇怪…但其實看一下原始碼就什麼都知道了…

我為什麼要了解這麼多細節…

Thanks

52

林信良 http://openhome.cc

[email protected]