Tomcat简易实现

请求处理过程

Web应用运行在Tomcat中,请求必定先到达Tomcat,Tomcat 对请求会进行如下处理:

  1. 提供Socket服务

    Tomcat的启动,必然是Socket服务,只不过它支持HTTP协议而已!

  2. 进行请求的分发

    一个Tomcat可以为多个Web应用提供服务,Tomcat可以把URL下发到不同的Web应用。

  3. 需要把请求和响应封装成request/response

    我们在Web应用这一层,从来没有封装过request/response,我们都是直接使用的,这就是因为Tomcat已经为你做好了!

实现过程

封装请求对象

通过输入流,对HTTP 协议进行解析,拿到HTTP请求头的方法以及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
public class MyRequest {

private String url = null;
private String method = null;

public MyRequest(InputStream inputStream) throws IOException {
String httpRequest = "";
byte[] httpRequestBytes = new byte[1024];
int length = 0;
if ((length = inputStream.read(httpRequestBytes)) > 0) {
httpRequest = new String(httpRequestBytes, 0, length);
/*
GET /girl HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36
*/
String httpHead = httpRequest.split("\n")[0];
url = httpHead.split("\\s")[1];
method = httpHead.split("\\s")[0];
System.out.println(httpHead);
}
}

public String getUrl() {
return url;
}

public String getMethod() {
return method;
}
}

封装响应对象

基于HTTP协议的格式进行输出写入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyResponse {

private OutputStream outputStream;

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

public void write(String content) throws IOException {
StringBuffer httpReponse = new StringBuffer();
httpReponse.append("HTTP/1.1 200 OK\n")
.append("Content-Type: text/html\n")
.append("\r\n")
.append("<html><body>")
.append(content)
.append("</body></html>");

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

Servlet请求处理基类

Tomcat是满足Servlet规范的容器,那么自然Tomcat需要提供API。这里看到Servlet常见的doGet/doPost/service方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class MyServlet {

public abstract void doGet(MyRequest myRequest, MyResponse myResponse);

public abstract void doPost(MyRequest myRequest, MyResponse myResponse);

public void service(MyRequest myRequest, MyResponse myResponse) {
if (myRequest.getMethod().equalsIgnoreCase("POST")) {
doPost(myRequest, myResponse);
} else if (myRequest.getMethod().equalsIgnoreCase("GET")) {
doGet(myRequest, myResponse);
}
}
}

Servlet实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FindGirlServlet extends MyServlet {

@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("get girl...");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("post girl...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HelloWorldServlet extends MyServlet {

@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("get world...");
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("post world...");
} catch (IOException e) {
e.printStackTrace();
}
}
}

Servlet配置

在servlet开发中,会在web.xml中通过来进行指定哪个URL交给哪个servlet进行处理。

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;
}
}
1
2
3
4
5
6
7
8
9
public class ServletMappingConfig {

public static List<ServletMapping> servletMappingList = new ArrayList<>();

static {
servletMappingList.add(new ServletMapping("findGirl", "/girl", FindGirlServlet.class.getName()));
servletMappingList.add(new ServletMapping("helloWorld", "/world", HelloWorldServlet.class.getName()));
}
}

启动类

Tomcat的处理流程,即把URL对应处理的Servlet关系形成,解析HTTP协议,封装请求/响应对象,利用反射实例化具体的Servlet进行处理即可。

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
public class MyTomcat {

private int port = 8080;

private Map<String, String> urlServletMap = new HashMap<>();

public MyTomcat(int port) {
this.port = port;
}

public void start() {

//初始化 URL与对应处理的servlet的关系
initServletMapping();

ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("MyTomcat is 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();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

private void initServletMapping() {
for (ServletMapping servletMapping : ServletMappingConfig.servletMappingList) {
urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz());
}
}

private void dispatch(MyRequest myRequest, MyResponse myResponse) {
String clazz = urlServletMap.get(myRequest.getUrl());
if (clazz != null && !clazz.equals("")) {
//反射
try {
Class<MyServlet> myServletClass = (Class<MyServlet>) Class.forName(clazz);
MyServlet myServlet = myServletClass.newInstance();
myServlet.service(myRequest, myResponse);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
new MyTomcat(8080).start();
}
}
1
2
3
4
5
6
7
8
# http://localhost:8080/girl

Connected to the target VM, address: '127.0.0.1:14711', transport: 'socket'
MyTomcat is start...
GET /girl HTTP/1.1
GET /favicon.ico HTTP/1.1
GET /favicon.ico HTTP/1.1
GET /favicon.ico HTTP/1.1
0%