实现一个简易的tomcat
总结
学习到了servlet的接受和分发请求的流程。
步骤
步骤一
先创建一个SpringBoot。这个不难。然后配置了一些日志信息就没了。
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<artifactId>spring-boot-starter-log4j2</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
</plugin>
</plugins>
</build>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration name="zzb-tomcat">
<Properties>
<Property name="LOGGING_PATTERN">
%d{yyyy-MM-dd HH:mm:ss.SSS} {%-5level} [%t] {%c{1.}.%M(%L)}: %msg%n
</Property>
</Properties>
<Appenders>
<!-- info level logger console -->
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="${LOGGING_PATTERN}"/>
</Console>
<!-- error level logger console -->
<Console name="STDERR" target="SYSTEM_ERR">
<PatternLayout pattern="${LOGGING_PATTERN}"/>
<ThresholdFilter level="error"/>
</Console>
<!-- logger async append -->
<Async name="ASYNC">
<AppenderRef ref="STDOUT"/>
<AppenderRef ref="STDERR"/>
</Async>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="ASYNC"/>
</Root>
<Logger name="com.tomcat.demo" level="DEBUG"/>
</Loggers>
</Configuration>
步骤二
接下来都是代码
Request请求
@Log4j2
@Data
public class MyRequest {
/**
* 请求地址
*/
private String url;
/**
* 请求方法
*/
private String method;
public MyRequest(InputStream inputStream) {
StringBuffer httpRequest = new StringBuffer();
byte[] httpRequestByte = new byte[1024];
int length = 0;
try {
if ((length = inputStream.read(httpRequestByte)) > 0) {
httpRequest.append(new String(httpRequestByte, 0, length));
}
log.info("httpRequest = [{}]", httpRequest);
if ("".equals(httpRequest.toString())) {
return;
}
String httpHead = httpRequest.toString().split("\n")[0];
this.url = httpHead.split("\\s")[1];
this.method = httpHead.split("\\s")[0];
log.info("MyRequests = [{}]", this);
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:这里的httpRequest判空的原因是因为我在用PostMan测试post请求时,发现PostMan会先发一次空的无用链接。所以这里为了项目能运行下去所以判空
Response请求
@Data
@AllArgsConstructor
public class MyResponse {
private OutputStream outputStream;
public void write (String content) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("HTTP/1.1 200 OK\n")
.append("Content-type:text/html\n")
.append("\r\n")
.append("<html><head><title>Hello World</title></head><body>")
.append(content)
.append("</body><html>");
try {
outputStream.write(stringBuilder .toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
写一个父类的servlet,负责判断请求
@Log4j2
public abstract class MyServlet {
private static final String POST = "POST";
private static final String GET = "GET";
/**
* get方法
* @param request 请求
* @param response 回应
*/
protected abstract void doGet(MyRequest request, MyResponse response);
/**
* post方法
* @param request 请求
* @param response 回应
*/
protected abstract void doPost(MyRequest request, MyResponse response);
public void service(MyRequest request, MyResponse response) {
if (POST.equalsIgnoreCase(request.getMethod())) {
doPost(request, response);
} else if (GET.equalsIgnoreCase(request.getMethod())) {
doGet(request, response);
} else {
log.info("方法不是GET或POST");
}
}
}
步骤三
基本类和父类写完了。接下来要写子类servlet(业务)去实现父类了
student类
public class StudentServlet extends MyServlet {
@Override
protected void doGet(MyRequest request, MyResponse response) {
response.write("I am a student ---- GET");
}
@Override
protected void doPost(MyRequest request, MyResponse response) {
response.write("I am a student ---- POST");
}
}
teacher类
public class TeacherServlet extends MyServlet {
@Override
protected void doGet(MyRequest request, MyResponse response) {
response.write("I am a teacher ---- GET");
}
@Override
protected void doPost(MyRequest request, MyResponse response) {
response.write("I am a teacher ---- POST");
}
}
favicon
public class FaviconServlet extends MyServlet {
@Override
protected void doGet(MyRequest request, MyResponse response) {
response.write("Favicon ---- GET");
}
@Override
protected void doPost(MyRequest request, MyResponse response) {
response.write("Favicon ---- POST");
}
}
步骤四
还记得最早开始学servlet的时候,要在xml文件里映射。这里就不做xml的解析了直接写个类存储,然后写个map里映射
servletMapping对象类
@Data
public class ServletMapping {
private String servletName;
private String url;
private String clazz;
public ServletMapping(String servletName, String url, String clazz) {
this.servletName = servletName;
this.url = url;
this.clazz = clazz;
}
}
配置信息映射
public class ServletMappingConfig {
public static List<ServletMapping> servletMappings = new ArrayList<ServletMapping>(16);
static {
servletMappings
.add(new ServletMapping("student", "/student", "com.tomcat.demo.one.StudentServlet"));
servletMappings
.add(new ServletMapping("teacher", "/teacher", "com.tomcat.demo.one.TeacherServlet"));
servletMappings
.add(new ServletMapping("favicon", "/favicon.ico", "com.tomcat.demo.one.FaviconServlet"));
}
}
步骤五
初始化配置,分发请求,返回结果
@Log4j2
@Data
@AllArgsConstructor
public class MyTomcat {
private Integer port;
private static final Map<String, String> URL_SERVLET_MAPPING = new HashMap<>(16);
@SuppressWarnings("InfiniteLoopStatement")
public void start() {
// 初始化请求映射关系
initServletMapping();
try {
ServerSocket serverSocket = new ServerSocket(port);
log.info("My Tomcat begin start");
while (true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
MyRequest myRequest = new MyRequest(inputStream);
MyResponse myResponse = new MyResponse(outputStream);
//分发请求
dispatch(myRequest, myResponse);
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void dispatch(MyRequest request, MyResponse response) {
String clazz = URL_SERVLET_MAPPING.get(request.getUrl());
if (null == clazz) {
log.debug("找不到{}", request.getUrl());
return;
}
try {
Class<?> servletClass = Class.forName(clazz);
MyServlet myServlet = (MyServlet) servletClass.getDeclaredConstructor().newInstance();
myServlet.service(request, response);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
private void initServletMapping() {
ServletMappingConfig.servletMappings
.forEach(src -> URL_SERVLET_MAPPING.put(src.getUrl(), src.getClazz()));
}
}
启动类
我们不用SpringBoot的启动类来启动。自己new个类写个Main方法即可。
public class Main {
public static void main(String[] args) {
new MyTomcat(8080).start();
}
}