Tomcat作为一个Http服务器,作用非常之大。本文主要研究Tomcat的执行流程和核心构成,并手动编写一个迷你版的Tomcat。
当⽤户请求某个URL资源时
1)HTTP服务器会把请求信息使⽤ServletRequest对象封装起来
2)进⼀步去调⽤Servlet容器中某个具体的Servlet
3)在 2)中,Servlet容器拿到请求后,根据URL和Servlet的映射关系,找到相应的Servlet
4)如果Servlet还没有被加载,就⽤反射机制创建这个Servlet,并调⽤Servlet的init⽅法来完成初始化
5)接着调⽤这个具体Servlet的service⽅法来处理请求,请求处理结果使⽤ServletResponse对象封装
6)把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端
Tomcat 设计了两个核⼼组件连接器(Connector)和容器(Container)来完成 Tomcat 的两⼤核⼼功能:
1、连接器,负责对外交流: 处理Socket连接,负责⽹络字节流与Request和Response对象的转化;(Coyote)
2、容器,负责内部处理:加载和管理Servlet,以及具体处理Request请求;(Catalina)
<dependency>
<groupId>dom4jgroupId>
<artifactId>dom4jartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>jaxengroupId>
<artifactId>jaxenartifactId>
<version>1.1.6version>
dependency>
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
* @Author wangyang
* @Create 2022/5/20 8:20
*/
public class MiniCatCtl {
static ThreadPoolExecutor threadPoolExecutor;
private Map<String, HttpServlet> servletMap = new HashMap<>();
static {
threadPoolExecutor =
new ThreadPoolExecutor(10, 20, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
public void start() throws Exception {
ServerSocket serverSocket = new ServerSocket(8080);
loadServlet();
while (true) {
Socket socket = serverSocket.accept();
threadPoolExecutor.execute(() -> {
try {
ServerRequest serverRequest = new ServerRequest(socket.getInputStream());
ServerResponse serverResponse = new ServerResponse(socket.getOutputStream());
// 静态资源处理
if (servletMap.get(serverRequest.getUrl()) == null) {
serverResponse.outputHtml(serverRequest.getUrl());
} else {
HttpServlet httpServlet = servletMap.get(serverRequest.getUrl());
httpServlet.service(serverRequest, serverResponse);
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception exception) {
exception.printStackTrace();
}
});
}
}
private void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// wangyang
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// com.wangyang.minicat.server.WangYangHttpServlet
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根据servlet-name的值找到url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
MiniCatCtl main = new MiniCatCtl();
main.start();
}
}
import java.io.IOException;
import java.io.InputStream;
/**
* @Author wangyang
* @Create 2022/5/20 16:16
*/
public class ServerRequest {
private String url;
private String method;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public ServerRequest(InputStream inputStream) {
try {
int available = 0;
while (available == 0) {
available = inputStream.available();
}
byte[] bytes = new byte[available];
inputStream.read(bytes);
String message = new String(bytes);
String[] split = message.split("\n");
String[] requestMsg = split[0].split(" ");
this.method = requestMsg[0];
this.url = requestMsg[1];
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("=====>method=" + method);
System.out.println("=====>url=" + url);
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* @Author wangyang
* @Create 2022/5/20 16:26
*/
public class ServerResponse {
private OutputStream outputStream;
public ServerResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
public void outputHtml(String path) throws IOException {
String absolutePath = this.getClass().getResource("/").getPath();
absolutePath = absolutePath.replaceAll("\\\\", "/") + path;
File file = new File(absolutePath);
if (file.exists() && file.isFile()) {
FileInputStream fileInputStream = new FileInputStream(file);
int count = 0;
while (count == 0) {
count =fileInputStream.available();
}
int resourceSize = count;
output(HttpProtocolUtil.getHttpHeader200(resourceSize));
//已经读取到的内容长度
long written = 0;
// 计划每次缓冲的长度
int byteSize = 1024;
byte[] bytes = new byte[byteSize];
if (written < resourceSize) {
if (written + byteSize > resourceSize) {
byteSize = (int)(resourceSize - written);
bytes = new byte[byteSize];
}
fileInputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written += byteSize;
}
} else {
// 404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
/**
* @Author wangyang
* @Create 2022/5/20 16:46
*/
public class HttpProtocolUtil {
public static String getHttpHeader200(Integer contentLength) {
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html \n" +
"Content-Length: " + contentLength + " \n" +
"\r\n";
}
public static String getHttpHeader404() {
String str404 = " 404 not found
";
return "HTTP/1.1 404 not found \n" +
"Content-Type: text/html \n" +
"Content-Length: " + str404.getBytes().length + " \n" +
"\r\n" + str404;
}
}
/**
* @Author wangyang
* @Create 2022/5/20 16:38
*/
public interface Servlet {
void init() throws Exception;
void destory() throws Exception;
void service(ServerRequest serverRequest, ServerResponse serverResponse) throws Exception;
}
/**
* @Author wangyang
* @Create 2022/5/20 16:38
*/
public abstract class HttpServlet implements Servlet {
public abstract void doGet(ServerRequest serverRequest, ServerResponse serverResponse);
public abstract void doPost(ServerRequest serverRequest, ServerResponse serverResponse);
@Override
public void service(ServerRequest serverRequest, ServerResponse serverResponse) throws Exception {
if ("GET".equalsIgnoreCase(serverRequest.getMethod())) {
doGet(serverRequest, serverResponse);
} else {
doPost(serverRequest, serverResponse);
}
}
}
/**
* @Author wangyang
* @Create 2022/5/20 16:44
*/
public class WangYangHttpServlet extends HttpServlet {
@Override
public void doGet(ServerRequest serverRequest, ServerResponse serverResponse) {
String content = " wangyangServlet get
";
try {
serverResponse.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void doPost(ServerRequest serverRequest, ServerResponse serverResponse) {
String content = " wangyangServlet post
";
try {
serverResponse.output(HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void init() throws Exception {
}
@Override
public void destory() throws Exception {
}
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>static resoucetitle>
head>
<body>
Hello Minicat-static resouce!
body>
html>
<web-app>
<servlet>
<servlet-name>wangyangservlet-name>
<servlet-class>com.wangyang.minicat.server.WangYangHttpServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>wangyangservlet-name>
<url-pattern>/indexurl-pattern>
servlet-mapping>
web-app>