如何写一个简单的TomCat服务器
1. 目标
1)提供服务,接收请求(Socket通信)
2)请求信息封装成Request对象(Response对象)
3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet)
4)资源返回给客户端浏览器
2.流程图
3.pom文件配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SimpleTomCat</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.资源配置
4.1 h1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>h1页面测试</title>
</head>
<body>
<h1>
手写TomCat静态页面1
</h1>
</body>
</html>
4.2 web.xml
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.tomcat.servlet.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/my</url-pattern>
</servlet-mapping>
</web-app>
5. Response 与 Request 对象
package com.tomcat.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Properties;
/**
* @author sz
* @DATE 2022/5/1 15:32
*/
@Data
public class Request {
/**
* 请求方法
*/
private String method;
/**
* 请求路径
*/
private String url;
private InputStream inputStream;
public Request(InputStream inputStream) throws IOException {
this.inputStream=inputStream;
byte[] bytes = new byte[1024];
int start;
String str = "";
while (-1 != (start = inputStream.read(bytes))) {
str += new String(bytes,0,start);
if (start<1024){
break;
}
}
String[] split = str.split("\\n");
//请求头第一行
try {
method = split[0].split(" ")[0];
} catch (Exception e) {
method="GET";
}
try {
url=split[0].split(" ")[1];
} catch (Exception e) {
url="/";
}
};
}
package com.tomcat.pojo;
import com.tomcat.util.HttpUtils;
import com.tomcat.util.ResponseUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.*;
import java.nio.charset.StandardCharsets;
/**
* @author sz
* @DATE 2022/5/1 15:32
*/
@Data
public class Response {
private OutputStream outputStream;
public Response(OutputStream outputStream){
this.outputStream=outputStream;
}
/**
* 根据url 返回静态资源
* @param str 静态资源url
*/
public void outputHtml(String str) throws IOException {
//获取资源的绝对路径
String absPath = ResponseUtils.getAbsPath(str);
File file = new File(absPath);
if (file.exists() && file.isFile()){
//输出资源
ResponseUtils.writeStaticHtml(new FileInputStream(file),outputStream);
}else {
//返回404
outputStream.write(HttpUtils.writeNotFound().getBytes(StandardCharsets.UTF_8));
outputStream.close();
}
}
public void output(String str) throws IOException {
outputStream.write(str.getBytes(StandardCharsets.UTF_8));
outputStream.close();
}
}
6. HttpUtils 与 ResponseUtils 工具类
package com.tomcat.util;
/**
* @author sz
* @DATE 2022/5/1 15:32
*/
public class HttpUtils {
private HttpUtils(){};
//输出成功的内容信息
public static String writeSuccess(){
String str = "HTTP/1.1 200 OK"+"\n"
+"Content-Type: text/html;charset=utf-8"+"\n"
+"\r\n";
return str;
}
//输出404的内容信息
public static String writeNotFound(){
String str = "HTTP/1.1 404 NotFound"+"\n"
+"Content-Type: text/html;charset=utf-8"+"\n"
+"\r\n"
+"<h1> HTTP/1.1 404 NotFound </h1>";
return str;
}
}
package com.tomcat.util;
import lombok.Data;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
/**
* @author sz
* @DATE 2022/5/1 16:11
*/
@Data
public class ResponseUtils {
private ResponseUtils() {
}
;
/**
* 获取静态资源的绝对路径
*
* @param path
* @return
*/
public static String getAbsPath(String path) {
return (ResponseUtils.class.getResource("/") + path.substring(1)).split("file:/")[1];
}
/**
* 输出静态资源
*
* @param inputStream
* @param outputStream
*/
public static void writeStaticHtml(InputStream inputStream, OutputStream outputStream) throws IOException {
//首先输出请求头
outputStream.write(HttpUtils.writeSuccess().getBytes(StandardCharsets.UTF_8));
//再输出请求体
byte[] bytes = new byte[1024];
int len;
if (-1 != (len = inputStream.read(bytes))) {
outputStream.write(bytes,0,len);
}
outputStream.close();
}
}
7. Serlvet 处理动态资源
package com.tomcat.inter;
import com.tomcat.pojo.Request;
import com.tomcat.pojo.Response;
public interface Servlet {
void init();
void destory();
void service(Request request, Response response);
}
package com.tomcat.abs;
import com.tomcat.inter.Servlet;
import com.tomcat.pojo.Request;
import com.tomcat.pojo.Response;
/**
* @author sz
* @DATE 2022/5/1 20:16
*/
public abstract class HttpServlet implements Servlet {
public abstract void doGet(Request request, Response response);
public abstract void doPost(Request request, Response response);
@Override
public void service(Request request, Response response) {
if ("GET".equals(request.getMethod())) {
doGet(request, response);
} else {
doPost(request, response);
}
}
}
package com.tomcat.servlet;
import com.tomcat.abs.HttpServlet;
import com.tomcat.pojo.Request;
import com.tomcat.pojo.Response;
import com.tomcat.util.HttpUtils;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* @author sz
* @DATE 2022/5/1 20:20
*/
public class MyServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
// try {
// TimeUnit.DAYS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
String context = "<h1> MyServlet GET </h1>" + "\r\n"+Thread.currentThread().getName();
try {
response.output(HttpUtils.writeSuccess()+context);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String context = "<h1> MyServlet POST </h1>"+ "\r\n"+Thread.currentThread().getName();
try {
response.output(HttpUtils.writeSuccess()+context);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init() {
}
@Override
public void destory() {
}
}
8. RequestProcess 请求处理器
package com.tomcat.process;
import com.tomcat.abs.HttpServlet;
import com.tomcat.pojo.Request;
import com.tomcat.pojo.Response;
import lombok.AllArgsConstructor;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
/**
* @author sz
* @DATE 2022/5/1 21:02
*/
@AllArgsConstructor
public class RequestProcess extends Thread{
private Socket accept;
private Map<String, HttpServlet> servletMap;
@Override
public void run() {
try {
//获取Request对象
Request request = new Request(accept.getInputStream());
//获取Response对象
Response response = new Response(accept.getOutputStream());
//写回资源
HttpServlet httpServlet = servletMap.get(request.getUrl());
if (null==httpServlet){
//为nulll,静态资源
response.outputHtml(request.getUrl());
}else {
//不为null 动态资源
httpServlet.service(request,response);
}
}catch (Exception e){
}
}
}
9. Main 方法 服务器启动入口
package com.tomcat.main;
import com.tomcat.abs.HttpServlet;
import com.tomcat.process.RequestProcess;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author sz
* @DATE 2022/5/1 10:30
*/
public class Main {
/*设置端口号*/
private static Integer port = 8080;
public static void main(String[] args) throws IOException {
Main main = new Main();
main.start();
}
public void start() throws IOException {
loadServlet();
//创建线程池
ThreadPoolExecutor tomcatThreadPool = new ThreadPoolExecutor(
10,
100,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
, new ThreadFactory() {
int i = 0;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "TomCat线程" + "---" + (++i));
}
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
//创建连接
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
//获取套接字
Socket accept = serverSocket.accept();
RequestProcess requestProcess = new RequestProcess(accept, servletMap);
tomcatThreadPool.execute(requestProcess);
}
}
private Map<String, HttpServlet> servletMap = new HashMap<String, HttpServlet>();
/**
* 加载解析web.xml,初始化Servlet
*/
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);
// <servlet-name>myServlet</servlet-name>
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// <servlet-class>com.tomcat.servlet.MyServlet</servlet-class>
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 + "']");
// /my
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 Integer getPort() {
return port;
}
public void setPort(Integer port) {
port = port;
}
}