《How Tomcat Works》读书笔记(二)

这是《How Tomcat Works》第一二章的读书笔记。第一张主要写了一个静态资源处理的web服务器,第二章加了对servlet的处理。

概述

基本的架构

  • HttpServer:表示Http服务器,与客户端通信,处理Http请求。
  • StaticResourceProcessor:对静态资源请求进行处理。
  • ServletProcessor:对Servlet资源请求进行处理。
  • Request:表示Http请求,实现了ServletRequest接口。
  • Response:表示Http响应,实现了ServletResponse接口。
  • RequestFacade/ResponseFacade:Request/Response的门面类。
  • PrimitiveServlet:表示一个Servlet类。

处理流程

实现

一些方法

HttpServer处理逻辑:

1
2
3
4
5
6
7
8
9
10
11
// check if this is a request for a servlet or
// a static resource
// a request for a servlet begins with "/servlet/"
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
}
else {
StaticResoureProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}

ServletProcessor中通过类加载器把.class文件动态的加载为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 void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// create a URLClassLoader
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// the forming of repository is taken from the
// createClassLoader method in
// org.apache.catalina.startup.ClassLoaderFactory
String repository =(new URL("file", null,
classPath.getCanonicalPath() + File.separator)).toString() ;
// the code for forming the URL is taken from
// the addRepository method in
// org.apache.catalina.loader.StandardClassLoader.
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
} catch (IOException e) {
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
// 使用门面设计模式
RequestFacade rquestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new RespondeFacade(response);
// 交由Servlet进行处理
servlet.service(requestFacade, responseFacade);
}
}

设计模式

在这里使用了门面的设计模式,目的是安全性。在StaticResourceProcessor.processor()方法中会把Request、Response对象传给Servelt进行处理,在后面程序猿处理Servlet请求时就可以把ServletRequest向下转型为Request对象,然后使用不该在此使用的方法,如:parse()方法。所以,在这里使用了门面设计模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ResponseFacade implements ServletResponse {
private ServletResponse servletResponse = null;

// 通过构造方法传入真正的Response,然后向上转型为ServletResponse
public ResponseFacade(Response response) {
this.servletResponse = response;
}
/**
* 后面都是实现ServlerResponse接口要实现的方法,就不写完了。
*/
@Override
public String getCharacterEncoding() {
return servletResponse.getCharacterEncoding();
}
@Override
public String getContentType() {
return servletResponse.getContentType();
}

注意点

HTTP协议

HTTP协议是应用层协议,它基于TCP/IP协议进行传输数据。在客户端与web服务器解析数据时,必须有相关的头部数据。所以web服务器给客户端发出响应,该响应必须加入头部数据。自己在写该代码时,就忘了加头部数据,出个bug,解决了一段时间才发现。PrimitiveServlet的service()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
String header = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html;charset=UTF-8\r\n"
+ "\r\n";
String content = "<html>\r\n"
+ "<head>"
+ "</head>"
+ "<body>"
+ "Primitive Servlet"
+ "</body>"
+ "</html>";
PrintWriter writer = res.getWriter();
writer.write(header);
writer.write(content);
writer.flush();
writer.close();
}

MIME类型

HTTP Respose header中有一个ContentType字段,它要求告诉客户端该Response正文的内容MIME类型,我们可以通过下面的API获得某一个文件的MIME类型。

1
String mimeType = URLConnection.getFileNameMap().getContentTypeFor(fileName)

Class文件

该系统使用到了URLClassLoader加载Class对象,ClassLoader把字节码(.class文件)加载为Class对象,所以需要编译好的.class文件。在Idea中Project中有个out目录,其中放的就是编译好的.class文件(只要你使用Idea运行过的.java文件)。

实现

与原书逻辑基本一致,由于自己加入了多线程技术,所以新增了Servlet容器(使用了Map),并且该容器一开始就加载所有的Servlet。

HttpServer:

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
/**
* Created by kanyuxia on 2017/4/24.
* HttpServer是模拟HTTP服务器:接受HTTP请求,响应静态资源或者Servlet资源
*/
public class HttpServer {
/**
* HttpServer端口号
*/
public static final int PORT = 10086;
/**
* Http静态文件根目录
*/
public static final String STATIC_RESOURCE_ROOT = "E:/java/HttpServer/staticresource";
/**
* Http中Servlet文件根目录
*/
public static final String SERVLET_ROOT = "E:/java/HttpServer/servlet";
/**
* 线程池
*/
private ExecutorService executorService;
/**
* 线程池大小
*/
public static final int THREAD_POOL_SIZE = 50;

HttpServer() {
executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
}

/**
* 启动HttpServer服务器
* 使用了try-with-resource: since jdk1.7
*/
public void start() {
// 创建ServerSocket
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
while (true) {
// 客户端连接
Socket socket = serverSocket.accept();
// 使用线程池处理该socket
ServerHandle serverHandle = new ServerHandle(socket);
executorService.execute(serverHandle);
}
} catch (IOException e) {
System.out.println(e);
}
}
}

Request和Response,省略部分方法

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* Created by kanyuxia on 2017/4/24
* Request类:实现了ServletRequest接口,如果HTTP请求是Servlet,则服务器创建ServletRequest和ServletResponse对象,
* 并传入Servlet的service()方法
*/
public class Request implements ServletRequest {
private final InputStream inputStream;

private String url;

public Request(InputStream inputStream) {
this.inputStream = inputStream;
}
/**
* 解析请求URL
*/
public void parse() {
BufferedInputStream in = new BufferedInputStream(inputStream);
StringBuilder result = new StringBuilder(1024);
byte[] buffer = new byte[1024];
int readNum = 0;
try {
readNum = in.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 0; i < readNum; i++) {
result.append((char) buffer[i]);
}
// 解析URL
int start = result.toString().indexOf(" ") + 1;
int end = result.toString().indexOf(" ", start);
this.url = result.toString().substring(start, end);
}
}

/**
* Created by kanyuxia on 2017/4/24.
*/
public class Response implements ServletResponse {
private final Request request;

private final OutputStream outputStream;

public Response(Request request, OutputStream outputStream) {
this.request = request;
this.outputStream = outputStream;
}

/**
* 发送静态资源方法
* @throws IOException
*/
public void sendStaticResource() throws IOException {
BufferedOutputStream out = new BufferedOutputStream(outputStream);
//请求的本地静态文件地址
File file = new File(HttpServer.STATIC_RESOURCE_ROOT + request.getUrl());
// 文件存在
if (file.exists()) {
String mimeType = URLConnection.getFileNameMap().getContentTypeFor(file.getName());
String header = "HTTP/1.1 200 OK\r\n"
+ "Content-Length: " + file.length() + "\r\n"
+ "Content-Type: " + mimeType + "; charset=UTF-8\r\n"
+ "\r\n";
// 发送header
out.write(header.getBytes());
out.flush();
// 发送content
byte[] buffer = new byte[1024];
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
for (int b = in.read(buffer); b != -1; b = in.read(buffer)) {
out.write(buffer, 0, b);
out.flush();
}
} catch (IOException e) {
System.out.println();
}
// 关闭流
try {
out.close();
} catch (IOException e) {
System.out.println();
}
return;
}
// 文件不存在
sendNotFound();
}

public void sendNotFound() {
BufferedOutputStream out = new BufferedOutputStream(outputStream);
// 文件不存在
String header = "HTTP/1.1 404 NOT FOUND\r\n"
+ "\r\n";
String content = "<html>\r\n" +
"<head>" +
"</head>" +
"<body>" +
"File Not Found" +
"</body>" +
"</html>";
// 发送数据
try {
out.write(header.getBytes());
out.write(content.getBytes());
out.flush();
} catch (IOException e) {
System.out.println(e);
} finally {
// 关闭流
try {
out.close();
} catch (IOException e) {
System.out.println(e);
}
}
}
}

StaticResourceProcessor,处理静态资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Created by kanyuxia on 2017/4/19.
* HttpServer静态资源处理类
*/
public class StaticResourceProcessor {
/**
* 静态资源处理方法
* @param request 请求对象
* @param response 响应对象
*/
public void process(Request request, Response response) {
try {
response.sendStaticResource();
} catch (IOException e) {
e.printStackTrace();
}
}
}

ServletProcessor:

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
/**
* Created by kanyuxia on 2017/4/24.
* HttpServer Servelt请求处理类
*/
public class ServletProcessor {
/**
* 处理Servlet请求方法
* @param request 请求对象
* @param response 响应对象
*/
public void process(Request request, Response response) {
String url = request.getUrl();
String servletName = url.substring(url.lastIndexOf("/") + 1);
// 从容器中拿到该Servlet
Servlet servlet = ServletContainer.container.get(servletName);
if (servlet != null) {
// 使用门面模式
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet.service(requestFacade, responseFacade);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
// Servlet不存在
response.sendNotFound();
}
}

ServletContainer:

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
/**
* Created by kanyuxia on 2017/4/24.
* Servlet容器:管理Servlet.
*/
public class ServletContainer {
/**
* 存放Servlet
*/
public static Map<String, Servlet> container = new HashMap<>();

/**
* 初始化所有的Servlet
*/
@SuppressWarnings("unchecked")
public void init() {
// 创建URLClassLoader
URLClassLoader classLoader = null;
try {
// 创建URL
URL[] urls = new URL[1];
File classPath = new File(HttpServer.SERVLET_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
URLStreamHandler streamHandler = null;
urls[0] = new URL(null, repository, streamHandler);
classLoader = new URLClassLoader(urls);
} catch (IOException e) {
System.out.println();
}
// 获得指定目录下的所有.class文件
File path = new File(HttpServer.SERVLET_ROOT);
File[] files = path.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.toString().endsWith(".class");
}
});
// 加载所有的.class文件
for (File file : files) {
String servletName = file.getName().substring(0, file.getName().indexOf("."));
Class<Servlet> servletClass = null;
try {
servletClass = (Class<Servlet>) classLoader.loadClass(servletName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Servlet servlet = null;
try {
servlet = servletClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
ServletContainer.container.put(servletName, servlet);
}
}
}