創作緣由
平時使用 tomcat 等 web 伺服器不可謂不多,但是一直一知半解。
於是想著自己實現一個簡單版本,學習一下 tomcat 的精髓。
系列教程
從零手寫實現 apache Tomcat-01-入門介紹
從零手寫實現 apache Tomcat-02-web.xml 入門詳細介紹
從零手寫實現 tomcat-03-基本的 socket 實現
從零手寫實現 tomcat-04-請求和響應的抽象
從零手寫實現 tomcat-05-servlet 處理支援
從零手寫實現 tomcat-06-servlet bio/thread/nio/netty 池化處理
從零手寫實現 tomcat-07-war 如何解析處理三方的 war 包?
從零手寫實現 tomcat-08-tomcat 如何與 springboot 整合?
從零手寫實現 tomcat-09-servlet 處理類
從零手寫實現 tomcat-10-static resource 靜態資原始檔
從零手寫實現 tomcat-11-filter 過濾器
從零手寫實現 tomcat-12-listener 監聽器
前言
還記得我們最初 web.xml 中的 servlet 嗎?
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<!-- servlet 配置 -->
<servlet>
<servlet-name>my</servlet-name>
<servlet-class>com.github.houbb.minicat.support.servlet.MyMiniCatHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>my</servlet-name>
<url-pattern>/my</url-pattern>
</servlet-mapping>
</web-app>
servlet 是什麼?我們又該如何解析實現呢?
servlet 是什麼?
Servlet可以被看作是Tomcat中的一個“服務員”。
就像餐廳裡的服務員負責接待顧客、接收點餐、上菜等任務一樣,Servlet在Web伺服器中負責處理網路請求,並返回響應結果。
Servlet的主要工作包括:
-
接收請求:當瀏覽器(客戶端)傳送一個HTTP請求到Tomcat時,這個請求會被對映到一個特定的Servlet。
-
處理請求:Servlet會根據請求的型別(比如GET或POST)和內容,執行相應的處理邏輯。比如,如果請求是要顯示一個網頁,Servlet就會生成這個網頁的內容。
-
生成響應:處理完請求後,Servlet會生成一個HTTP響應,這個響應包含了客戶端需要的資訊,比如網頁內容、圖片、影片等。
-
返回響應:最後,Servlet把生成的響應返回給客戶端,客戶端收到響應後,就可以展示網頁或者進行其他操作。
servlet 處理流程
-
客戶端(比如瀏覽器)傳送一個HTTP請求到Tomcat。
-
Tomcat的請求分發器(RequestDispatcher)會根據請求的URL,找到對應的Servlet。
-
Tomcat呼叫Servlet的
service()
方法,把請求交給Servlet處理。在service()
方法內部,Servlet會根據HTTP請求的方法(GET、POST等)呼叫相應的處理方法。 -
Servlet處理請求,並生成響應。比如,如果是GET請求,Servlet可能會查詢資料庫,生成一個網頁;如果是POST請求,Servlet可能會處理表單資料,執行一些業務邏輯。
-
Servlet把生成的響應返回給Tomcat。
-
Tomcat把響應傳送回客戶端。
透過使用Servlet,你可以靈活地處理各種HTTP請求,並生成相應的響應。
舉個板栗
好的,讓我們用餐廳的例子來比喻Tomcat中的Servlet:
想象一下,你走進一家餐廳,服務員會過來接待你。
服務員會問你需要什麼服務,這就像是HTTP請求。在Tomcat中,Servlet就扮演了服務員的角色。
-
接收點餐:當顧客(客戶端)進入餐廳(訪問網站),服務員(Servlet)會過來記錄顧客的點餐(接收HTTP請求)。
-
處理點餐:服務員會根據顧客的點餐內容(請求型別,如GET或POST),去廚房(後端邏輯)準備食物(處理請求)。
-
上菜:食物準備好後,服務員會將食物(響應內容)端上桌(生成HTTP響應)。
-
結賬:顧客享用完畢後,服務員會拿來賬單(請求結束,返回響應),顧客結賬離開。
Servlet透過這種方式,可以處理各種型別的點餐(請求),無論是簡單的檢視選單(靜態頁面請求),還是複雜的定製菜品(複雜的業務邏輯請求)。
透過合理設計Servlet,餐廳(網站)可以提供豐富多樣的服務(功能)。
自己實現
介面定義
這裡就不定義了,直接複用 servlet 的標準 api
抽象類
我們實現一個基礎的抽象類:
package com.github.houbb.minicat.support.servlet;
import com.github.houbb.minicat.constant.HttpMethodType;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public abstract class AbstractMiniCatHttpServlet extends HttpServlet {
public abstract void doGet(HttpServletRequest request, HttpServletResponse response);
public abstract void doPost(HttpServletRequest request, HttpServletResponse response);
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
if(HttpMethodType.GET.getCode().equalsIgnoreCase(httpServletRequest.getMethod())) {
this.doGet(httpServletRequest, httpServletResponse);
return;
}
this.doPost(httpServletRequest, httpServletResponse);
}
}
介面實現
簡單的實現
package com.github.houbb.minicat.support.servlet;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.minicat.dto.IMiniCatResponse;
import com.github.houbb.minicat.dto.MiniCatResponseBio;
import com.github.houbb.minicat.util.InnerHttpUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 僅用於測試
*
* @since 0.3.0
* @author 老馬嘯西風
*/
public class MyMiniCatHttpServlet extends AbstractMiniCatHttpServlet {
private static final Log logger = LogFactory.getLog(MyMiniCatHttpServlet.class);
// 方法實現
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
logger.info("MyMiniCatServlet-get");
// 模擬耗時
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String content = "MyMiniCatServlet-get";
IMiniCatResponse miniCatResponse = (IMiniCatResponse) response;
miniCatResponse.write(InnerHttpUtil.http200Resp(content));
logger.info("MyMiniCatServlet-get-end");
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) {
String content = "MyMiniCatServlet-post";
IMiniCatResponse miniCatResponse = (IMiniCatResponse) response;
miniCatResponse.write(InnerHttpUtil.http200Resp(content));
}
}
應用啟動解析
那麼, 應該如何解析處理 servlet 呢?
DefaultServletManager
定義一個 servlet 的管理類
package com.github.houbb.minicat.support.servlet.manager;
import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.minicat.exception.MiniCatException;
import javax.servlet.http.HttpServlet;
import java.util.HashMap;
import java.util.Map;
/**
* servlet 管理
*
* 基於 web.xml 的讀取解析
* @since 0.5.0
* @author 老馬嘯西風
*/
public class DefaultServletManager implements IServletManager {
// 基礎屬性省略
@Override
public void register(String url, HttpServlet servlet) {
logger.info("[MiniCat] register servlet, url={}, servlet={}", url, servlet.getClass().getName());
servletMap.put(url, servlet);
}
@Override
public HttpServlet getServlet(String url) {
return servletMap.get(url);
}
}
register 的時機
以本地的 web.xml 解析為例
- 解析對應的 web.xml 中的 servlet
protected void processWebServlet(Element root, final IServletManager servletManager) {
Map<String, String> servletClassNameMap = new HashMap<>();
Map<String, String> urlPatternMap = new HashMap<>();
List<Element> servletElements = root.elements("servlet");
for (Element servletElement : servletElements) {
String servletName = servletElement.elementText("servlet-name");
String servletClass = servletElement.elementText("servlet-class");
servletClassNameMap.put(servletName, servletClass);
}
List<Element> urlMappingElements = root.elements("servlet-mapping");
for (Element urlElem : urlMappingElements) {
String servletName = urlElem.elementText("servlet-name");
String urlPattern = urlElem.elementText("url-pattern");
urlPatternMap.put(servletName, urlPattern);
}
handleServletConfigMap(servletClassNameMap, urlPatternMap, servletManager);
}
- 註冊對應的資訊
protected void handleServletConfigMap(Map<String, String> servletClassNameMap, Map<String, String> urlPatternMap, final IServletManager servletManager) {
try {
for (Map.Entry<String, String> urlPatternEntry : urlPatternMap.entrySet()) {
String servletName = urlPatternEntry.getKey();
String urlPattern = urlPatternEntry.getValue();
String className = servletClassNameMap.get(servletName);
if (StringUtil.isEmpty(className)) {
throw new MiniCatException("className not found for servletName: " + servletName);
}
Class servletClazz = Class.forName(className);
HttpServlet httpServlet = (HttpServlet) servletClazz.newInstance();
// 構建
String fullUrlPattern = buildFullUrlPattern(urlPattern);
servletManager.register(fullUrlPattern, httpServlet);
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new MiniCatException(e);
}
}
呼叫的時機
servlet 註冊好了,我們什麼時候使用呢?
當然是根據請求地址 url 分發處理了。
public class ServletRequestDispatcher implements IRequestDispatcher {
private static final Log logger = LogFactory.getLog(ServletRequestDispatcher.class);
public void dispatch(final IMiniCatRequest request,
final IMiniCatResponse response,
final MiniCatContextConfig config) {
final IServletManager servletManager = config.getServletManager();
// 直接和 servlet 對映
final String requestUrl = request.getUrl();
HttpServlet httpServlet = servletManager.getServlet(requestUrl);
if(httpServlet == null) {
logger.warn("[MiniCat] requestUrl={} mapping not found", requestUrl);
response.write(InnerHttpUtil.http404Resp());
} else {
// 正常的邏輯處理
try {
httpServlet.service(request, response);
} catch (Exception e) {
logger.error("[MiniCat] http servlet handle meet ex", e);
throw new MiniCatException(e);
}
}
}
}
這樣,一個簡單的 servlet 處理流程就實現了。
開源地址
/\_/\
( o.o )
> ^ <
mini-cat 是簡易版本的 tomcat 實現。別稱【嗅虎】(心有猛虎,輕嗅薔薇。)
開源地址:https://github.com/houbb/minicat