大家也可以關(guān)注我的公眾號(hào): 漿果捕鼠草,文章也會(huì)同步更新歹茶,當(dāng)然盟广,公眾號(hào)還會(huì)有一些資源可以分享給大家~
手寫迷你版 Tomcat - Minicat
Minicat 的目標(biāo)
我們可以通過瀏覽器客戶端發(fā)送 http 請(qǐng)求廷粒, Minicat 可以接收到請(qǐng)求進(jìn)?處理鸳址,處理之后的結(jié)果可以返回瀏覽器客戶端。
基本方向
提供服務(wù)矮固,接收請(qǐng)求(Socket 通信)
請(qǐng)求信息封裝成 Request 對(duì)象失息,同樣響應(yīng)信息封裝成 Response 對(duì)象
客戶端請(qǐng)求資源,資源分為靜態(tài)資源(HTML)和動(dòng)態(tài)資源(Servlet)
資源返回給客戶端瀏覽器
迭代實(shí)現(xiàn)
我們實(shí)現(xiàn)時(shí)候呢档址,一步一步來盹兢,可以制定的小版本計(jì)劃
- V1.0 需求:瀏覽器請(qǐng)求 http://localhost:8080, 返回?個(gè)固定的字符串到??"Hello Minicat."
- V2.0 需求:封裝 Request 和 Response 對(duì)象,返回 HTML 靜態(tài)資源?件
- V3.0 需求:可以請(qǐng)求動(dòng)態(tài)資源(Servlet)
- V4.0 需求:可以多線程訪問
- V5.0 需求:在已有 Minicat 基礎(chǔ)上進(jìn)?步擴(kuò)展守伸,模擬出 webapps 部署效果,磁盤上放置?個(gè) webapps ?錄绎秒,webapps 中可以有多個(gè)項(xiàng)?,?如 demo1,demo2,demo3… 具體的項(xiàng)??如 demo1 中有 serlvet(也即為:servlet 是屬于具體某?個(gè)項(xiàng)?的 servlet)尼摹,這樣的話在 Minicat 初始化配置加載见芹,以及根據(jù)請(qǐng)求 url 查找對(duì)應(yīng) serlvet 時(shí)都需要進(jìn)?步處理。
V1.0 版本
環(huán)境搭建
確定好方向蠢涝,就進(jìn)行項(xiàng)目搭建開發(fā)
新建一個(gè) Maven 項(xiàng)目玄呛,并調(diào)整在 pom.xml
<groupId>site.suremotoo</groupId>
<artifactId>Minicat</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
編寫啟動(dòng)類 Bootstrap
public class Bootstrap {
/**
* 設(shè)定啟動(dòng)和監(jiān)聽端口
*/
private int port = 8080;
/**
* 啟動(dòng)函數(shù)
*
* @throws IOException
*/
public void start() throws IOException {
System.out.println("Minicat starting...");
String responseData = "Hello Minicat.";
ServerSocket socket = new ServerSocket(port);
while (true) {
Socket accept = socket.accept();
OutputStream outputStream = accept.getOutputStream();
String responseText = HttpProtocolUtil.getHttpHeader200(responseData.length()) + responseData;
outputStream.write(responseText.getBytes());
accept.close();
}
}
/**
* 啟動(dòng)入口
*
* @param args
*/
public static void main(String[] args) throws IOException {
Bootstrap bootstrap = new Bootstrap();
bootstrap.start();
}
}
HTTP 協(xié)議輔助類 HttpProtocolUtil
public class HttpProtocolUtil {
/**
* 200 狀態(tài)碼,頭信息
*
* @param contentLength 響應(yīng)信息長(zhǎng)度
* @return 200 header info
*/
public static String getHttpHeader200(long contentLength) {
return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n"
+ "Content-Length: " + contentLength + " \n" + "\r\n";
}
/**
* 為響應(yīng)碼 404 提供請(qǐng)求頭信息(此處也包含了數(shù)據(jù)內(nèi)容)
*
* @return 404 header info
*/
public static String getHttpHeader404() {
String str404 = "<h1>404 not found</h1>";
return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n"
+ "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404;
}
}
然后我們?cè)L問瀏覽器:http://localhost:8080和二,頁(yè)面顯示 Hello Minicat.,就說明成功啦徘铝。
這就完成 V1.0 版本了 ??
V2.0 版本
緊接著我們實(shí)現(xiàn)請(qǐng)求信息、響應(yīng)信息的封裝,即:Request
惕它、Response
的實(shí)現(xiàn)
public class Request {
/**
* 請(qǐng)求方式, eg: GET怕午、POST
*/
private String method;
/**
* 請(qǐng)求路徑,eg: /index.html
*/
private String url;
/**
* 請(qǐng)求信息輸入流 <br>
* 示例
* <pre>
* GET / HTTP/1.1
* Host: www.baidu.com
* Connection: keep-alive
* Pragma: no-cache
* Cache-Control: no-cache
* Upgrade-Insecure-Requests: 1
* User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
* </pre>
*/
private InputStream inputStream;
public Request() {
}
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
// requestString 參考:this.inputStream 示例
String requestString = new String(bytes);
// 按換行分隔
String[] requestStringArray = requestString.split("\\n");
// 讀取第一行數(shù)據(jù),即: GET / HTTP/1.1
String firstLine = requestStringArray[0];
// 把第一行數(shù)據(jù)按空格分隔
String[] firstLineArray = firstLine.split(" ");
this.method = firstLineArray[0];
this.url = firstLineArray[1];
}
}
Response
public class Response {
/**
* 響應(yīng)信息
*/
private OutputStream outputStream;
public Response() {
}
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
/**
* 根據(jù) url 拼接絕對(duì)路徑,根據(jù)絕對(duì)路徑再讀取文件資源淹魄,再響應(yīng)
*
* @param url 請(qǐng)求 url
*/
public void outputHtml(String url) throws IOException {
// 獲取 url 資源的全路徑
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(url);
File file = new File(absoluteResourcePath);
if (file.exists() && file.isFile()) {
// 輸出靜態(tài)資源
StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
} else {
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
我們也提取了一些共用類和函數(shù)郁惜,StaticResourceUtil
public class StaticResourceUtil {
/**
* 根據(jù)請(qǐng)求 url 獲取完整絕對(duì)路徑
*
* @param url 請(qǐng)求 url
* @return 完整絕對(duì)路徑
*/
public static String getAbsolutePath(String url) {
String path = StaticResourceUtil.class.getResource("/").getPath();
return path.replaceAll("\\\\", "/") + url;
}
/**
* 輸出靜態(tài)資源信息
*
* @param inputStream 靜態(tài)資源文件的讀取流
* @param outputStream 響應(yīng)的輸出流
* @throws IOException
*/
public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
int count = 0;
while (count == 0) {
count = inputStream.available();
}
// 輸出 http 請(qǐng)求頭,然后再輸出具體內(nèi)容
int resourceSize = count;
// 讀取內(nèi)容輸出
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
// 已經(jīng)讀取的內(nèi)容?度
long written = 0;
// 計(jì)劃每次緩沖的?度
int byteSize = 1024;
byte[] bytes = new byte[byteSize];
while (written < resourceSize) {
// 說明剩余未讀取??不??個(gè) 1024 ?度,那就按真實(shí)?度處理
if (written + byteSize > resourceSize) {
// 計(jì)算實(shí)際剩余內(nèi)容
byteSize = (int) (resourceSize - written);
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
// 計(jì)算已經(jīng)讀取的長(zhǎng)度
written += byteSize;
}
}
}
最后調(diào)整一下Bootstrap
中的start()
函數(shù)
public void start() throws IOException {
System.out.println("Minicat starting...");
ServerSocket socket = new ServerSocket(port);
while (true) {
Socket accept = socket.accept();
OutputStream outputStream = accept.getOutputStream();
// 分別封裝 Request 和 Response
Request request = new Request(accept.getInputStream());
Response response = new Response(outputStream);
// 根據(jù) request 中的 url甲锡,輸出
response.outputHtml(request.getUrl());
accept.close();
}
}
哦對(duì)了兆蕉,還缺少 1 個(gè)具體的 HTML 靜態(tài)資源?件,我們創(chuàng)建一個(gè)名為 index.html
的吧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Minicat</title>
</head>
<body>
<h1>Hello Minicat, index.html</h1>
</body>
</html>
然后我們?cè)L問瀏覽器:http://localhost:8080/index.html缤沦,頁(yè)面顯示 Hello Minicat, index.html,就說明成功啦恨樟。
這就完成 V2.0 版本了 ????
V3.0 版本
接下來就來實(shí)現(xiàn)請(qǐng)求動(dòng)態(tài)資源
我們先定義 Servlet
的接口,指定規(guī)范
public interface Servlet {
void init() throws Exception;
void destroy() throws Exception;
void service(Request request, Response response) throws Exception;
}
緊接著再完成 1 個(gè)公共的抽象父類 HttpServlet
public abstract class HttpServlet implements Servlet {
public abstract void doGet(Request request, Response response) throws Exception;
public abstract void doPost(Request request, Response response) throws Exception;
@Override
public void service(Request request, Response response) throws Exception {
String method = request.getMethod();
if ("GET".equalsIgnoreCase(method)) {
doGet(request, response);
} else {
doPost(request, response);
}
}
}
實(shí)際的 doGet
疚俱、doPost
由實(shí)際配置的業(yè)務(wù) Servlet
來實(shí)現(xiàn),那么我們就創(chuàng)建 1 個(gè)實(shí)際業(yè)務(wù): ShowServlet
public class ShowServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) throws Exception {
String repText = "<h1>ShowServlet by GET</h1>";
response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
}
@Override
public void doPost(Request request, Response response) throws Exception {
String repText = "<h1>ShowServlet by POST</h1>";
response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
}
@Override
public void init() throws Exception {}
@Override
public void destroy() throws Exception {}
}
我們?cè)僭陧?xiàng)目 resources
文件夾中創(chuàng)建 web.xml
缩多,并配置我們的業(yè)務(wù) Servlet: ShowServlet
的名稱和訪問路徑
<?xml version="1.0" encoding="utf-8"?>
<web-app>
<servlet>
<servlet-name>show</servlet-name>
<servlet-class>server.ShowServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>show</servlet-name>
<url-pattern>/show</url-pattern>
</servlet-mapping>
</web-app>
既然配置了 web.xml
呆奕,那我們還需要再對(duì)其進(jìn)行解析和處理
解析 XML,我們?cè)?pom.xml
里引入相關(guān)坐標(biāo)依賴
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
我們把處理邏輯寫在 Bootstrap
啟動(dòng)類中
/**
* 存放 Servlet信息衬吆,url: Servlet 實(shí)例
*/
private Map<String, HttpServlet> servletMap = new HashMap<>();
/**
* 加載配置的 Servlet
*
* @throws Exception
*/
public void loadServlet() throws Exception {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("http://servlet");
for (Element element : list) {
// <servlet-name>show</servlet-name>
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// <servlet-class>server.ShowServlet</servlet-class>
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根據(jù) servlet-name 的值找到 url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// /show
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance());
}
}
再調(diào)整 start
方法梁钾,在方法里執(zhí)行 loadServlet
函數(shù)來初始化 Servlet
public void start() throws Exception {
System.out.println("Minicat starting...");
// 此處加載并初始化 Servlet
loadServlet();
ServerSocket socket = new ServerSocket(port);
while (true) {
Socket accept = socket.accept();
OutputStream outputStream = accept.getOutputStream();
// 分別封裝 Request 和 Response
Request request = new Request(accept.getInputStream());
Response response = new Response(outputStream);
// 根據(jù) url 來獲取 Servlet
HttpServlet httpServlet = servletMap.get(request.getUrl());
// 如果 Servlet 為空,說明是靜態(tài)資源逊抡,不為空即為動(dòng)態(tài)資源姆泻,需要執(zhí)行 Servlet 里的方法
if (httpServlet == null) {
response.outputHtml(request.getUrl());
} else {
httpServlet.service(request, response);
}
accept.close();
}
}
然后我們?cè)L問瀏覽器: http://localhost:8080/index.html,頁(yè)面顯示 Hello Minicat, index.html,就說明靜態(tài)資源訪問成功啦冒嫡。
我們?cè)僭L問: http://localhost:8080/show, 頁(yè)面顯示 ShowServlet by GET,就說明動(dòng)態(tài)資源也訪問成功啦拇勃。
這就完成 V3.0 版本了 ??????
V4.0 版本
完成 V3.0 之后,3.0 還是有點(diǎn)問題的孝凌,比如在多線程訪問情況下
我們假設(shè)業(yè)務(wù) ShowServlet
里方咆,動(dòng)態(tài)資源處理延遲,這里使用 Thread.sleep
模擬延遲的情況
@Override
public void doGet(Request request, Response response) throws Exception {
// 模擬延遲
Thread.sleep(100000);
String repText = "<h1>ShowServlet by GET</h1>";
response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
}
然后再重新啟動(dòng)蟀架,先訪問:http://localhost:8080/show瓣赂,會(huì)發(fā)現(xiàn)一直在加載處理,此時(shí)再去訪問 http://localhost:8080/index.html片拍,也會(huì)一直在加載處理煌集。
意思就是我們?cè)L問動(dòng)態(tài)資源時(shí)候的一直等待,此時(shí)捌省,再去訪問靜態(tài)資源的時(shí)候苫纤,也會(huì)一直等待,這樣就不行了。
這里就可以使用線程來解決方面,每個(gè)請(qǐng)求過來的 Socket
都分配 1 個(gè)線程放钦,各自處理各自的請(qǐng)求
所以再在 Bootstrap
中的 start
方法做個(gè)改造
public void start() throws Exception {
System.out.println("Minicat starting...");
loadServlet();
ServerSocket socket = new ServerSocket(port);
while (true) {
Socket accept = socket.accept();
// 多線程改造,每個(gè) socket 是個(gè)單獨(dú)的線程
RequestProcessor requestProcessor = new RequestProcessor(accept, servletMap);
requestProcessor.start();
}
}
RequestProcessor
就集成 Thread
恭金,重寫 run
函數(shù)操禀,實(shí)現(xiàn)具體的輸入輸出的處理
public class RequestProcessor extends Thread {
private Socket socket;
private Map<String, HttpServlet> servletMap;
public RequestProcessor() {
}
public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {
this.socket = socket;
this.servletMap = servletMap;
}
@Override
public void run() {
try {
OutputStream outputStream = socket.getOutputStream();
// 分別封裝 Request 和 Response
Request request = new Request(socket.getInputStream());
Response response = new Response(outputStream);
HttpServlet httpServlet = servletMap.get(request.getUrl());
if (httpServlet == null) {
response.outputHtml(request.getUrl());
} else {
httpServlet.service(request, response);
}
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
每個(gè)都分配個(gè)線程,有點(diǎn)消耗資源横腿,所以我們?cè)偕?jí)下颓屑,使用線程池,所以再對(duì) start
函數(shù)進(jìn)行一次簡(jiǎn)單的調(diào)整
public void start() throws Exception {
System.out.println("Minicat starting...");
loadServlet();
// 定義一個(gè)線程池
int corePoolSize = 10;
int maximumPoolSize =50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
ServerSocket socket = new ServerSocket(port);
while (true) {
Socket accept = socket.accept();
// 多線程改造耿焊,每個(gè) socket 是個(gè)單獨(dú)的線程
RequestProcessor requestProcessor = new RequestProcessor(accept, servletMap);
// 線程池執(zhí)行
threadPoolExecutor.execute(requestProcessor);
}
}
這就完成 V4.0 版本了 ????????
V5.0 版本
開始之前揪惦,我們先捋一下之前實(shí)現(xiàn) Minicat
的步驟,來看個(gè)圖
5.0 版本來了罗侯,我們要模擬出 webapps 部署效果器腋,這個(gè)功能說簡(jiǎn)單點(diǎn)了,就是把一個(gè)指定的文件夾 webapps 下面的所有的 Servlet
給讀取出來并加載钩杰,然后在請(qǐng)求的時(shí)候去解析對(duì)應(yīng)的 servlet 就行了纫塌。
那么我們就 resources
里寫個(gè)配置文件:server.xml
來配置指定的文件夾路徑信息,為了模仿實(shí)際的 Tomcat讲弄,我們?cè)偌訋讉€(gè)標(biāo)簽
<Server>
<Service name="Mojave">
<Connector port="8080"/>
<Engine defaultHost="localhost">
<!-- appBase 就是指定的文件夾 -->
<Host name="localhost" appBase="/Users/suremotoo/Documents/webapps"/>
</Engine>
</Service>
</Server>
而業(yè)務(wù) Servlet 呢措左,肯定要在單獨(dú)的項(xiàng)目中寫了,Minicat 是要部署加載它們的避除,后面再說怎披。
根據(jù)面向?qū)ο蟮乃枷耄覀冊(cè)诰帉懸恍?POJO 類
Context
就是在指定的 webapps 目錄中的瓶摆,每個(gè)項(xiàng)目凉逛,每個(gè)項(xiàng)目里有自己的 Servlet 集合
public class Context {
public Context() {
}
public Context(Map<String, HttpServlet> servletMap) {
this.servletMap = servletMap;
}
// Context 中的 Servlet
private Map<String, HttpServlet> servletMap;
public Map<String, HttpServlet> getServletMap() {
return servletMap;
}
public void setServletMap(Map<String, HttpServlet> servletMap) {
this.servletMap = servletMap;
}
}
Host
作為配置中的主機(jī)信息,每個(gè)主機(jī)下面有自己的 Context 項(xiàng)目集合
public class Host {
public Host() {
}
public Host(Map<String, Context> contextMap) {
this.contextMap = contextMap;
}
// hHst 中的 Context
private Map<String, Context> contextMap;
public Map<String, Context> getContextMap() {
return contextMap;
}
public void setContextMap(Map<String, Context> contextMap) {
this.contextMap = contextMap;
}
}
Mapper
其實(shí)指的是 Service,每個(gè) Service 下面有自己的 Host 主機(jī)集合
public class Mapper {
// Service 中的 Host
private Map<String, Host> hostMap;
public Mapper(Map<String, Host> hostMap) {
this.hostMap = hostMap;
}
public Map<String, Host> getHostMap() {
return hostMap;
}
public void setHostMap(Map<String, Host> hostMap) {
this.hostMap = hostMap;
}
}
Server
指 Minicat 服務(wù)實(shí)例, 1 個(gè) Minicat 就 1 個(gè) Server,每個(gè) Server 下面有自己的 Mapper 集合
public class Server {
// Server 里的 Service
private Map<String, Mapper> serviceMap;
public Server() {
}
public Server(Map<String, Mapper> serviceMap) {
this.serviceMap = serviceMap;
}
public Map<String, Mapper> getServiceMap() {
return serviceMap;
}
public void setServiceMap(Map<String, Mapper> serviceMap) {
this.serviceMap = serviceMap;
}
}
POJO 類完成后赏壹,就開始改造程序入口 Bootstrap
了鱼炒,要加載指定目錄下的文件,所以我們要改造 loadServlet
/**
* 加載實(shí)際項(xiàng)目里配置的 Servlet
*
* @throws Exception
*/
@SuppressWarnings({"unchecked"})
public Context loadContextServlet(String path) throws Exception {
String webPath = path + "/web.xml";
if (!(new File(webPath).exists())) {
System.out.println("not found " + webPath);
return null;
}
InputStream resourceAsStream = new FileInputStream(webPath);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("http://servlet");
Map<String, HttpServlet> servletMap = new HashMap<>(16);
for (Element element : list) {
// <servlet-name>show</servlet-name>
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// <servlet-class>server.ShowServlet</servlet-class>
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根據(jù) servlet-name 的值找到 url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// /show
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
// 自定義類加載器蝌借,來加載 webapps 目錄下的 class
WebClassLoader webClassLoader = new WebClassLoader();
Class<?> aClass = webClassLoader.findClass(path, servletClass);
servletMap.put(urlPattern, (HttpServlet) aClass.getDeclaredConstructor().newInstance());
}
return new Context(servletMap);
}
/**
* 加載 server.xml昔瞧,解析并初始化 webapps 下面的各個(gè)項(xiàng)目的 servlet
*/
@SuppressWarnings({"unchecked"})
public void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
// 解析 server 標(biāo)簽
Element serverElement = (Element) rootElement.selectSingleNode("http://Server");
// 解析 server 下的 Service 標(biāo)簽
List<Element> serviceNodes = serverElement.selectNodes("http://Service");
// 存儲(chǔ)各個(gè) Host
Map<String, Host> hostMap = new HashMap<>(8);
//遍歷 service
for (Element service : serviceNodes) {
String serviceName = service.attributeValue("name");
Element engineNode = (Element) service.selectSingleNode("http://Engine");
List<Element> hostNodes = engineNode.selectNodes("http://Host");
// 存儲(chǔ)有多少個(gè)項(xiàng)目
Map<String, Context> contextMap = new HashMap<>(8);
for (Element hostNo : hostNodes) {
String hostName = hostNo.attributeValue("name");
String appBase = hostNo.attributeValue("appBase");
File file = new File(appBase);
if (!file.exists() || file.list() == null) {
break;
}
String[] list = file.list();
//遍歷子文件夾,即:實(shí)際的項(xiàng)目列表
for (String path : list) {
//將項(xiàng)目封裝成 context菩佑,并保存入map
contextMap.put(path, loadContextServlet(appBase + "/" + path));
}
// hsot:port
// eg: localhost:8080
hostMap.put(hostName + ":" + port, new Host(contextMap));
}
serviceMap.put(serviceName, new Mapper(hostMap));
}
} catch (Exception e) {
e.printStackTrace();
}
}
代碼有點(diǎn)長(zhǎng)自晰,稍微說明下理解起來還是比較簡(jiǎn)單的:
先解析 server.xml
,然后根據(jù)配置的指定文件夾目錄去解析稍坯,目錄下面的每個(gè)文件夾就是一個(gè) Context
酬荞;
而 Context 里的 servlet 信息搓劫,都會(huì)配置在 Context 自己的 web.xml
中;
所以再解析每個(gè) Context 里自己的 web.xml混巧,封裝所有的 Servlet
當(dāng)然了枪向,Context
、Host
咧党、Mapper
秘蛔、Server
也都會(huì)一并封裝
封裝所有的 Servlet 的時(shí)候,需要自己去寫個(gè)類加載器去實(shí)例化傍衡,所以加了個(gè)自定義的類加載器
public class WebClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String basePath, String className) {
byte[] classBytes = getClassBytes(basePath, className);
return defineClass(className, classBytes, 0, classBytes.length);
}
/**
* 讀取類的字節(jié)碼
*
* @param basePath 根路徑
* @param className 類的全限定名
* @return servlet 的字節(jié)碼信息
* @throws IOException
*/
private byte[] getClassBytes(String basePath, String className) {
InputStream in = null;
ByteArrayOutputStream out = null;
String path = basePath + File.separatorChar +
className.replace('.', File.separatorChar) + ".class";
try {
in = new FileInputStream(path);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
基礎(chǔ)的工作都做好了深员,就差解析了,我們要改造 RequestProcessor
public class RequestProcessor extends Thread {
private Socket socket;
private Server server;
public RequestProcessor() {
}
public RequestProcessor(Socket socket, Server server) {
this.socket = socket;
this.server = server;
}
@Override
public void run() {
try {
OutputStream outputStream = socket.getOutputStream();
// 分別封裝 Request 和 Response
Request request = new Request(socket.getInputStream());
Response response = new Response(outputStream);
HttpServlet httpServlet = findHttpServlet(request);
if (httpServlet == null) {
response.outputHtml(request.getUrl());
} else {
httpServlet.service(request, response);
}
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根據(jù)請(qǐng)求信息找到對(duì)應(yīng)業(yè)務(wù) Servlet
* <pre>
* GET web-greet/greet HTTP/1.1
* Host: suremotoo.com
* </pre>
*
* @param request
* @return 具體要執(zhí)行的 servlet
*/
private HttpServlet findHttpServlet(Request request) {
HttpServlet businessServlet = null;
Map<String, Mapper> serviceMap = server.getServiceMap();
for (String key : serviceMap.keySet()) {
String hostName = request.getHost();
Map<String, Host> hostMap = serviceMap.get(key).getHostMap();
Host host = hostMap.get(hostName);
if (host != null) {
Map<String, Context> contextMap = host.getContextMap();
// 處理 url
// eg: web-greet/greet
String url = request.getUrl();
String[] urlPattern = url.split("/");
String contextName = urlPattern[1];
String servletStr = "/";
if (urlPattern.length > 2) {
servletStr += urlPattern[2];
}
// 獲取上下文
Context context = contextMap.get(contextName);
if (context != null) {
Map<String, HttpServlet> servletMap = context.getServletMap();
businessServlet = servletMap.get(servletStr);
}
}
}
return businessServlet;
}
}
核心就是 findHttpServlet
方法蛙埂,從 Server倦畅、Mapper、Host绣的、Context
依次取出叠赐,直到最后的 Servlet 配置集合,
根據(jù) url 找到對(duì)應(yīng)的 Servlet 執(zhí)行
至于我們的測(cè)試項(xiàng)目屡江,用于部署到 webapps 目錄嘛燎悍,我這里就簡(jiǎn)單說一下就行
先把 Minicat 打個(gè) jar 包,給測(cè)試項(xiàng)目用盼理!
參考 Minicat 的目錄,可以建立 maven
項(xiàng)目俄删,編寫自定義的業(yè)務(wù) Servlet 宏怔,然后在 resources
文件夾中,建立 web.xml
文件畴椰,用于配置自定義的 Servlet
???? 注意啦:而自定義的 Servlet 一定要 extends
Minicat 中的 HttpServet
k铩!
至此就 V5.0 就大功告成 ??????????
各版本實(shí)現(xiàn)回顧
附-精美 PDF 版本
????????另外特殊福利 ?????? 關(guān)注我的公眾號(hào): 漿果捕鼠草斜脂,發(fā)送關(guān)鍵字: Minicat 精美
即可獲得本文的精美 PDF 版本哦抓艳!