深入理解Tomcat之二:自己动手实现一个简单的Tomcat

学习一个新的知识的过程就是 看别人的文章、听别人讲、自己查资料、自己给别人讲。我们对于新知识的认识成都以及理解深度都是在整个过程中不断的加深的。所以我一直提倡大家要乐于分享,当你给别人用组织系统化的语言或者文章将你头脑中的知识输出来,你就会发现,你对之前知识的理解又加深了一个程度。 而这一篇手写tomcat,其实也是我在学习tomcat架构的过程中,模仿别人的代码自己再手敲一遍,最后将思路和实现过程整理成文,输出给大家。

主要需求

  • 监听请求端口

  • 封装请求和返回

  • 对请求进行处理

上面就是mini tomcat的类图

各个类

MyRequest

自己封装的请求类,相当于servlet中的HttpRequest。
  • inputStream来自于socket的输入流,用浏览器访问的时候就会包含了整个请求的报文

  • 解析http请求头的第一行 拿出协议中的 GET 或者 POST 还有请求url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

/**
* 自己封装的请求
*/
public class MyRequest {
private String url;
private String method;

public MyRequest(InputStream inputStream) throws IOException {
String httpRequest = "";
byte[] requestBytes = new byte[1024];
int length = inputStream.read(requestBytes);

if(length>0){
httpRequest = new String(requestBytes, 0, length);
}
// 解析内容
// 第一行是http头部内容
String httpHead = httpRequest.split("\n")[0];
url = httpHead.split("\\s")[1];
method = httpHead.split("\\s")[0];

System.out.println("接收到请求-------》");
System.out.println("请求信息 :" + toString());
}

@Override
public String toString() {
return "url: " + url + ", method: " + method;
}

public String getUrl() {
return url;
}


public String getMethod() {
return method;
}

}

MyResponse

  • 要点在于手动按照http协议的格式进行响应,这样浏览器才可以识别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 自己封装的响应
*/
public class MyResponse {
private OutputStream outputStream;

public MyResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}

public void write(String content) throws IOException {
if(outputStream!=null) {
StringBuffer httpResponse = new StringBuffer();

// 按照http响应格式输出
httpResponse.append("HTTP/1.1 200 OK\n")
.append("Content-Type: text/html\n")
.append("\r\n")
.append("<html><body>")
.append(content)
.append("</body></html>");

System.out.println("返回信息:"+httpResponse.toString());

outputStream.write(httpResponse.toString().getBytes());
outputStream.close();
}
}
}

MyServlet

抽象的Servlet,可以继承它来有很多不同的实现。

  • service 方法根据请求的方法分发到get或者post进行处理,这里与
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 自己封装的Servlet
* 继承后做不同的实现
*/
public abstract class MyServlet {
public abstract void doGet(MyRequest httpRequest, MyResponse httpResponse);

public abstract void doPost(MyRequest httpRequest, MyResponse httpResponse);

public void service(MyRequest request, MyResponse response) {
if(request!=null) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
doPost(request, response);
} else if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request, response);
}
}
}
}

HelloServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Servlet的实现类,在post get中添加具体的业务逻辑
* 以前使用servlet编程的时候也是这样的实现方法
*/
public class HelloServlet extends MyServlet {
@Override
public void doGet(MyRequest httpRequest, MyResponse httpResponse) {
try {
httpResponse.write("get method in hello servlet !!");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void doPost(MyRequest httpRequest, MyResponse httpResponse) {
try {
httpResponse.write("post method in hello servlet");
} catch (IOException e) {
e.printStackTrace();
}
}
}

ServletMapping

其实是一个Bean,简单封装了配置信息,方便我们读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 String getServletName() {
return servletName;
}

public void setServletName(String servletName) {
this.servletName = servletName;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getClazz() {
return clazz;
}

public void setClazz(String clazz) {
this.clazz = clazz;
}
}

ServletMappingConfig

存着一个列表来保存配置,真正tomcat也不是这样实现的。我们只是为了效果方便实现。

1
2
3
4
5
6
7
8
9
10
11
/**
* 这里由于是demo 采用简洁的方式先进行配置
*/
public class ServletMappingConfig {
public static List<ServletMapping> config = new ArrayList<ServletMapping>();

// 在真正的tomcat中是扫描web.xml的配置来初始化ServletMapping
static {
config.add(new ServletMapping("hello", "/hello", "com.practice.HelloServlet"));
}
}

MyTomcat

minitomcat的核心类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

public class MyTomcat {
private int port = 8080;
private HashMap<String, String> mapping = new HashMap<String, String>();
public MyTomcat(int port) {
this.port = port;
}

/**
* 使用socket不断等候接收新的请求
*/
void start() {
initservletMappings();

ServerSocket socket = null;

try {
socket = new ServerSocket(port);
System.out.println("Tomcat 启动成功~~");

while (true) {
Socket accept = socket.accept();
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();

MyRequest myRequest = new MyRequest(inputStream);
MyResponse myResponse = new MyResponse(outputStream);

dispatch(myRequest, myResponse);

// socket.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

/**
* 读取配置文件初始化servletMapping
*/
private void initservletMappings() {
for (ServletMapping servletMapping : ServletMappingConfig.config) {
mapping.put(servletMapping.getUrl(), servletMapping.getClazz());
}
}

/**
* 读取配置找到对应的Servlet类,使用反射创建实例处理请求
* @param request
* @param response
*/
private void dispatch(MyRequest request, MyResponse response) {
String url = request.getUrl();
String clazz = mapping.get(url);

if(clazz==null || clazz==""){
System.out.println("没有找到请求对应的链接:" + url);
System.out.println();
return;
}

try {
Class<MyServlet> aClass = (Class<MyServlet>) Class.forName(clazz);
MyServlet myServlet = aClass.newInstance();
myServlet.service(request, response);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("不存在该类");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}

}

/**
* 启动tomcat
* @param args
*/
public static void main(String[] args) {
new MyTomcat(8081).start();
}
}

最后我们运行main方法,在浏览器中访问localhost:8081/hello 就可以看到效果啦!

总结

这个tomcat很mini ,只简单实现了基本的功能,大家可以在这个基础上不断添加其他的功能,让这个minitomcat越来越接近真正的tomct!!