手写tomcat

[TOC]

概述

看了廖雪峰老师的《手写tomcat》的一些想法等。

https://liaoxuefeng.com/books/jerrymouse/introduction/index.html

创建一个maven项目

源码包下的:

connector 写 http相关的

engine 写 servlet相关的

引入日志框架

pom.xml

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
<?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>mytomcat</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>

<logback.version>1.4.6</logback.version>
<slf4j.version>2.0.7</slf4j.version>
</properties>

<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>

</project>

日志测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @date : 2024/8/28 09:53
* @description: 日志测试
*/
public class LogTest {

private final static Logger log = LoggerFactory.getLogger(LogTest.class);

public static void main(String[] args) {
log.info("xxx");
log.error("xxx", new RuntimeException());
}

}

http服务器

使用java中的网络IO实现。

相当于字符串的处理。

使用java中的HttpServer。

简化开发。

使用java框架netty实现。

线程方面的控制。

代码实现

socket 代码为了演示其实就是流操作以至于是字符串操作。此手写tomcat采用httpserver来实现。

socket

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
import java.io.*;  
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleHttpServer {

public static void main(String[] args) throws IOException {
final int PORT = 8080;
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server is listening on port " + PORT);

while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected from " + clientSocket.getInetAddress().getHostAddress());

new ClientHandler(clientSocket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}

static class ClientHandler extends Thread {
private final Socket socket;

public ClientHandler(Socket socket) {
this.socket = socket;
}

@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

String inputLine;
StringBuilder response = new StringBuilder();

// 读取HTTP请求行
if ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);

// 简化处理,只返回静态HTML内容
response.append("HTTP/1.1 200 OK\r\n");
response.append("Content-Type: text/html\r\n");
response.append("\r\n");
response.append("<html><body><h1>Hello, HTTP Server!</h1></body></html>");

// 发送响应
out.println(response.toString());
}

socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

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
50
51
52
53
54
55
56
57
58
59
60
61
package connector;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

public class HttpConnector implements HttpHandler, AutoCloseable {

final Logger logger = LoggerFactory.getLogger(getClass());

final HttpServer httpServer;

final String host;

final int port;

public HttpConnector( String host, int port) throws IOException {
this.host = host;
this.port = port;
this.httpServer = HttpServer.create(new InetSocketAddress(this.host, this.port), 0);
this.httpServer.createContext("/", this);
this.httpServer.start();
}

@Override
public void handle(HttpExchange exchange) throws IOException {
// 获取请求方法、URI、Path、Query等:
String method = exchange.getRequestMethod();
URI uri = exchange.getRequestURI();
String path = uri.getPath();
String query = uri.getRawQuery();
logger.info("{}: {}?{}", method, path, query);
// 输出响应的Header:
Headers respHeaders = exchange.getResponseHeaders();
respHeaders.set("Content-Type", "text/html; charset=utf-8");
respHeaders.set("Cache-Control", "no-cache");
// 设置200响应:
exchange.sendResponseHeaders(200, 0);
// 输出响应的内容:
String s = "<h1>Hello, world.</h1><p>" + LocalDateTime.now().withNano(0) + "</p>";
try (OutputStream out = exchange.getResponseBody()) {
out.write(s.getBytes(StandardCharsets.UTF_8));
}
}

@Override
public void close() throws Exception {
this.httpServer.stop(3);
}

}
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
import connector.HttpConnector;

/**
* @date : 2024/8/28 10:26
* @description: 启动类
*/
public class Start {

public static void main(String[] args) {
String host = "0.0.0.0";
int port = 8080;
try (HttpConnector connector = new HttpConnector(host, port)) {
for (;;) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

}

servlet规范实现

我们实现的是一个servlet规范的web服务器,就是开发者开发servlet,加载到咱们的web服务器就可以运行。

实现HttpServletRequest与HttpServletResponse

上面httpserver中请求与响应都使用的HttpExchange对象,但是我们是一个支持servlet的web服务器,所以需要转化成HttpServletRequest与HttpServletResponse。

这里廖老师使用了适配器(adapter)模式,没感觉出来啥好处,以后思想够了再看。

这两个类是对我来说是比较困难的。

在httpserver类的handle方法中调用process方法,入参有HttpServletRequest和HttpServletResponse,然后按照servlet的写法实现以前handle的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
String method = request.getMethod();
String path = request.getRequestURI();

logger.info("{}: {}", method, path);

response.setContentType("text/html; charset=utf-8");
response.setHeader("Cache-Control", "no-cache");
response.setStatus(200);
// 输出响应的内容:
String s = "<h1>Hello, world.</h1><p>" + LocalDateTime.now().withNano(0) + "</p>";
try (OutputStream out = response.getOutputStream()) {
out.write(s.getBytes(StandardCharsets.UTF_8));
}
}
1
2
3
4
5
6
@Override
public void handle(HttpExchange exchange) throws IOException {
HttpServletRequestImpl request = new HttpServletRequestImpl(exchange);
HttpServletResponseImpl response = new HttpServletResponseImpl(exchange);
process(request, response);
}

实现你调用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class HttpServletRequestImpl implements HttpServletRequest {

private HttpExchange httpExchange;

public HttpServletRequestImpl(HttpExchange httpExchange) {
this.httpExchange = httpExchange;
}

@Override
public String getMethod() {
return this.httpExchange.getRequestMethod();
}

@Override
public String getRequestURI() {
return this.httpExchange.getRequestURI().getPath();
}
}
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
public class HttpServletResponseImpl implements HttpServletResponse {

private HttpExchange httpExchange;

private Headers respHeaders;

private int status = 200;

public HttpServletResponseImpl(HttpExchange httpExchange) {
this.httpExchange = httpExchange;
this.respHeaders = httpExchange.getResponseHeaders();
}

public void commitStatus() throws IOException {
int responseLength = -1;
if (status == 200) {
responseLength = 0;
}
this.httpExchange.sendResponseHeaders(this.status, responseLength);
}

@Override
public void setContentType(String s) {
this.respHeaders.set("Content-Type", s);
}

@Override
public void setHeader(String s, String s1) {
this.respHeaders.set(s, s1);
}

@Override
public void setStatus(int i) {
this.status = i;
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
commitStatus();
return new ServletOutputStreamImpl(this.httpExchange.getResponseBody());
}
}
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
package engine;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;


import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;

/**
* @date : 2024/8/28 17:06
* @description: ServletOutputStream实现类
*/
public class ServletOutputStreamImpl extends ServletOutputStream {

private final OutputStream output;
private WriteListener writeListener = null;

public ServletOutputStreamImpl(OutputStream output) {
this.output = output;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void close() throws IOException {
this.output.close();
}

@Override
public void setWriteListener(WriteListener writeListener) {
this.writeListener = writeListener;
try {
this.writeListener.onWritePossible();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
public void write(int b) throws IOException {
try {
this.output.write(b);
} catch (IOException e) {
if (this.writeListener != null) {
this.writeListener.onError(e);
}
throw e;
}
}

}

这时启动就已经完成最上面httpserver的功能。接下来就是填充request和response的方法。

Request所有简单方法的实现

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
public class HttpServletRequestImpl implements HttpServletRequest {

private HttpExchange httpExchange;

String characterEncoding;

String requestId = null;

public HttpServletRequestImpl(HttpExchange httpExchange) {
this.httpExchange = httpExchange;
}

@Override
public String getMethod() {
return this.httpExchange.getRequestMethod();
}

@Override
public String getRequestURI() {
return this.httpExchange.getRequestURI().getPath();
}

@Override
public String toString() {
return String.format("HttpServletRequestImpl@%s[%s:%s]", Integer.toHexString(hashCode()), getMethod(), getRequestURI());
}

// address and port
@Override
public String getRemoteAddr() {
InetSocketAddress address = this.httpExchange.getRemoteAddress();
String addr = address.getHostString();
return addr;
}

@Override
public String getRemoteHost() {
// avoid DNS lookup by IP:
return getRemoteAddr();
}

@Override
public int getRemotePort() {
InetSocketAddress address = this.httpExchange.getRemoteAddress();
return address.getPort();
}

@Override
public String getLocalAddr() {
InetSocketAddress address = this.httpExchange.getLocalAddress();
return address.getHostString();
}

@Override
public String getLocalName() {
// avoid DNS lookup:
return getLocalAddr();
}

@Override
public int getLocalPort() {
InetSocketAddress address = this.httpExchange.getLocalAddress();
return address.getPort();
}

@Override
public String getCharacterEncoding() {
if (this.characterEncoding == null || this.characterEncoding.isEmpty()) {
this.characterEncoding = "UTF-8";
}
return this.characterEncoding;
}

@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
this.characterEncoding = env;
}

@Override
public String getProtocol() {
return "HTTP/1.1";
}

@Override
public String getScheme() {
String scheme = "http";
return scheme;
}

@Override
public int getServerPort() {
InetSocketAddress address = this.httpExchange.getLocalAddress();
return address.getPort();
}

@Override
public boolean isSecure() {
return "https".equals(getScheme().toLowerCase());
}

@Override
public DispatcherType getDispatcherType() {
return DispatcherType.REQUEST;
}

@Override
public String getRequestId() {
if (this.requestId == null) {
this.requestId = UUID.randomUUID().toString();
}
return this.requestId;
}

@Override
public String getQueryString() {
return this.httpExchange.getRequestURI().getRawQuery();
}

@Override
public StringBuffer getRequestURL() {
StringBuffer sb = new StringBuffer(128);
sb.append(getScheme()).append("://").append(getServerName()).append(':').append(getServerPort()).append(getRequestURI());
return sb;
}

@Override
public String getServletPath() {
return getRequestURI();
}


// -----------------------不支持方法开始

@Override
public RequestDispatcher getRequestDispatcher(String path) {
// do not support request dispatcher:
return null;
}

@Override
public AsyncContext startAsync() throws IllegalStateException {
throw new IllegalStateException("Async is not supported.");
}

@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
throw new IllegalStateException("Async is not supported.");
}

@Override
public boolean isAsyncStarted() {
return false;
}

@Override
public boolean isAsyncSupported() {
return false;
}

@Override
public AsyncContext getAsyncContext() {
throw new IllegalStateException("Async is not supported.");
}

@Override
public ServletConnection getServletConnection() {
throw new UnsupportedOperationException("getServletConnection");
}

@Override
public String getAuthType() {
// not support auth:
return null;
}

@Override
public String getPathInfo() {
return null;
}

@Override
public String getProtocolRequestId() {
// empty string for http 1.x:
return "";
}

@Override
public String getContextPath() {
// root context path:
return "";
}

@Override
public String getRemoteUser() {
// not support auth:
return null;
}

@Override
public boolean isUserInRole(String role) {
// not support auth:
return false;
}

@Override
public Principal getUserPrincipal() {
// not support auth:
return null;
}

@Override
public String getRequestedSessionId() {
return null;
}


@Override
public String changeSessionId() {
throw new UnsupportedOperationException("changeSessionId() is not supported.");
}

@Override
public boolean isRequestedSessionIdValid() {
return false;
}

@Override
public boolean isRequestedSessionIdFromCookie() {
return true;
}

@Override
public boolean isRequestedSessionIdFromURL() {
return false;
}

@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
// not support auth:
return false;
}

@Override
public void login(String username, String password) throws ServletException {
// not support auth:
}

@Override
public void logout() throws ServletException {
// not support auth:
}

@Override
public Collection<Part> getParts() throws IOException, ServletException {
// not suport multipart:
return List.of();
}

@Override
public Part getPart(String name) throws IOException, ServletException {
// not suport multipart:
return null;
}

@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> handlerClass) throws IOException, ServletException {
// not suport websocket:
return null;
}
// -----------------------不支持方法结束
}

Request添加header相关的类及方法

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
package utils;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

/**
* 用于处理浏览器中使用GMT时间的header等位置
*/
public class DateUtils {

static final ZoneId GMT = ZoneId.of("Z");

public static long parseDateTimeGMT(String s) {
ZonedDateTime zdt = ZonedDateTime.parse(s, DateTimeFormatter.RFC_1123_DATE_TIME);
return zdt.toInstant().toEpochMilli();
}

public static String formatDateTimeGMT(long ts) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(ts), GMT);
return zdt.format(DateTimeFormatter.RFC_1123_DATE_TIME);
}

public static void main(String[] args) {
String dateStr = formatDateTimeGMT(System.currentTimeMillis());
System.out.println(dateStr);
long millisecond = parseDateTimeGMT(dateStr);
System.out.println(millisecond);
}

}
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
119
120
package utils;

import com.sun.net.httpserver.Headers;
import jakarta.servlet.http.Cookie;

import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;

public class HttpUtils {

static final Pattern QUERY_SPLIT = Pattern.compile("\\&");

public static final Locale DEFAULT_LOCALE = Locale.getDefault();

public static final List<Locale> DEFAULT_LOCALES = List.of(DEFAULT_LOCALE);

public static List<Locale> parseLocales(String acceptLanguage) {
// try parse Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
String[] ss = acceptLanguage.split(",");
List<Locale> locales = new ArrayList<>(ss.length);
for (String s : ss) {
int n = s.indexOf(';');
String name = n < 0 ? s : s.substring(0, n);
int m = name.indexOf('-');
if (m < 0) {
locales.add(Locale.forLanguageTag(name));
} else {
locales.add(Locale.forLanguageTag(name.substring(0, m)));
}
}
return locales.isEmpty() ? DEFAULT_LOCALES : locales;
}

/**
* 解析查询参数
* 把get请求的查询参数 转 为map对象 并name和value 经过url解码
*/
public static Map<String, List<String>> parseQuery(String query, Charset charset) {
if (query == null || query.isEmpty()) {
return Map.of();
}
String[] ss = QUERY_SPLIT.split(query);
Map<String, List<String>> map = new HashMap<>();
for (String s : ss) {
int n = s.indexOf('=');
if (n >= 1) {
String key = s.substring(0, n);
String value = s.substring(n + 1);
List<String> exist = map.get(key);
if (exist == null) {
exist = new ArrayList<>(4);
map.put(key, exist);
}
exist.add(URLDecoder.decode(value, charset));
}
}
return map;
}

/**
* 解析查询参数
* 把get请求的查询参数 转 为map对象 并name和value 经过url解码
* 使用utf-8编码
*/
public static Map<String, List<String>> parseQuery(String query) {
return parseQuery(query, StandardCharsets.UTF_8);
}

/**
* 从headers中根据name获取value
* @param headers
* @param name
* @return
*/
public static String getHeader(Headers headers, String name) {
List<String> values = headers.get(name);
return values == null || values.isEmpty() ? null : values.get(0);
}

/**
* cookie字符串转换为cookie对象
* @param cookieValue
* @return
*/
public static Cookie[] parseCookies(String cookieValue) {
if (cookieValue == null) {
return null;
}
// 去除字符串两端的空白字符,包括空格、制表符、换行符等
// 能够去除Unicode空格字符,而不仅仅是ASCII空格字符。
cookieValue = cookieValue.strip();
if (cookieValue.isEmpty()) {
return null;
}
String[] ss = cookieValue.split(";");
Cookie[] cookies = new Cookie[ss.length];
for (int i = 0; i < ss.length; i++) {
String s = ss[i].strip();
int pos = s.indexOf('=');
String name = s;
String value = "";
if (pos >= 0) {
name = s.substring(0, pos);
value = s.substring(pos + 1);
}
cookies[i] = new Cookie(name, value);
}
return cookies;
}

public static void main(String[] args) {
System.out.println(parseLocales("zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"));
System.out.println(parseQuery("xxx?aaa=bbb&ccc=ddd"));
System.out.println(parseCookies("a=1;b=2"));
}

}

在request类添加属性与赋值:

1
2
3
4
5
6
7
8
9
10
11
12
    Headers headers;

int contentLength = 0;

public HttpServletRequestImpl(HttpExchange httpExchange) {
this.httpExchange = httpExchange;
this.headers = this.httpExchange.getRequestHeaders();
String method = getRequestURI();
if ("POST".equals(method) || "PUT".equals(method) || "DELETE".equals(method) || "PATCH".equals(method)) {
this.contentLength = getIntHeader("Content-Length");
}
}

header相关的方法

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
@Override
public String getHeader(String name) {
List<String> values = this.headers.get(name);
return values == null || values.isEmpty() ? null : values.get(0);
}

@Override
public long getDateHeader(String name) {
String value = getHeader(name);
if (value == null) {
return -1;
}
try {
return DateUtils.parseDateTimeGMT(value);
} catch (DateTimeParseException e) {
throw new IllegalArgumentException("Cannot parse date header: " + value);
}
}

@Override
public Enumeration<String> getHeaders(String name) {
List<String> hs = this.headers.get(name);
if (hs == null) {
return Collections.emptyEnumeration();
}
return Collections.enumeration(hs);
}

@Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(this.headers.keySet());
}

@Override
public int getIntHeader(String name) {
String value = getHeader(name);
return value == null ? -1 : Integer.parseInt(value);
}

@Override
public int getContentLength() {
return this.contentLength;
}

@Override
public long getContentLengthLong() {
return this.contentLength;
}

@Override
public String getContentType() {
return getHeader("Content-Type");
}

@Override
public String getServerName() {
String header = getHeader("Host");
if (header == null) {
InetSocketAddress address = this.httpExchange.getLocalAddress();
header = address.getHostString();
}
return header;
}

@Override
public Cookie[] getCookies() {
String cookieValue = this.getHeader("Cookie");
return HttpUtils.parseCookies(cookieValue);
}

@Override
public Locale getLocale() {
String langs = getHeader("Accept-Language");
if (langs == null) {
return HttpUtils.DEFAULT_LOCALE;
}
return HttpUtils.parseLocales(langs).get(0);
}

@Override
public Enumeration<Locale> getLocales() {
String langs = getHeader("Accept-Language");
if (langs == null) {
return Collections.enumeration(HttpUtils.DEFAULT_LOCALES);
}
return Collections.enumeration(HttpUtils.parseLocales(langs));
}

request添加流相关的类及方法

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
package engine;

import java.io.IOException;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;

public class ServletInputStreamImpl extends ServletInputStream {

private final byte[] data;
private int lastIndexRetrieved = -1;
private ReadListener readListener = null;

public ServletInputStreamImpl(byte[] data) {
this.data = data;
}

@Override
public boolean isFinished() {
return lastIndexRetrieved == data.length - 1;
}

@Override
public boolean isReady() {
return true;
}

@Override
public void setReadListener(ReadListener readListener) {
this.readListener = readListener;
if (!isFinished()) {
try {
readListener.onDataAvailable();
} catch (IOException e) {
readListener.onError(e);
}
} else {
try {
readListener.onAllDataRead();
} catch (IOException e) {
readListener.onError(e);
}
}
}

@Override
public int read() throws IOException {
if (lastIndexRetrieved < data.length) {
lastIndexRetrieved++;
int n = data[lastIndexRetrieved];
if (readListener != null && isFinished()) {
try {
readListener.onAllDataRead();
} catch (IOException ex) {
readListener.onError(ex);
throw ex;
}
}
return n;
}
return -1;
}

@Override
public int available() throws IOException {
return data.length - lastIndexRetrieved - 1;
}

@Override
public void close() throws IOException {
lastIndexRetrieved = data.length - 1;
}

}
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

@Override
public ServletInputStream getInputStream() throws IOException {
byte[] bytes = null;
try (InputStream input = this.httpExchange.getRequestBody()) {
bytes = input.readAllBytes();
}
return new ServletInputStreamImpl(bytes);
}

@Override
public BufferedReader getReader() throws IOException {
byte[] bytes = null;
try (InputStream input = this.httpExchange.getRequestBody()) {
bytes = input.readAllBytes();
}
return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes), getCharacterEncoding()));
}

@Override
public Map<String, String[]> getParameterMap() {
if (this.parameters == null) {
this.parameters = initParameters();
}
return this.parameters;
}

Map<String, String[]> initParameters() {
Charset charset = Charset.forName(getCharacterEncoding());
Map<String, List<String>> params = new HashMap<>();
String query = this.httpExchange.getRequestURI().getRawQuery();
if (query != null) {
params = HttpUtils.parseQuery(query, charset);
}
if ("POST".equals(this.httpExchange.getRequestMethod())) {
String value = HttpUtils.getHeader(this.httpExchange.getRequestHeaders(), "Content-Type");
if (value != null && value.startsWith("application/x-www-form-urlencoded")) {
String requestBody;
byte[] bytes;
try (InputStream input = this.httpExchange.getRequestBody()) {
bytes = input.readAllBytes();
requestBody = new String(bytes, charset);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Map<String, List<String>> postParams = HttpUtils.parseQuery(requestBody, charset);
// merge:
for (String key : postParams.keySet()) {
List<String> postValues = postParams.get(key);
List<String> queryValues = params.get(key);
if (queryValues == null) {
params.put(key, postValues);
} else {
queryValues.addAll(postValues);
}
}
}
}
if (params.isEmpty()) {
return Map.of();
}
// convert:
Map<String, String[]> paramsMap = new HashMap<>();
for (String key : params.keySet()) {
List<String> values = params.get(key);
paramsMap.put(key, values.toArray(String[]::new));
}
return paramsMap;
}

@Override
public String getParameter(String name) {
String[] values = getParameterValues(name);
if (values == null) {
return null;
}
return values[0];
}

@Override
public Enumeration<String> getParameterNames() {
return Collections.enumeration(getParameterMap().keySet());
}

@Override
public String[] getParameterValues(String name) {
return getParameterMap().get(name);
}

request添加attribute相关的类及方法

因为attribute类存在session/servletcontent/request等中,所以抽像出来

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
package engine.support;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* Lazy proxy which hode a Map.
*/
public class LazyMap<V> {

private Map<String, V> map = null;
private final boolean concurrent;

public LazyMap(boolean concurrent) {
this.concurrent = concurrent;
}

protected V get(String name) {
if (this.map == null) {
return null;
}
return this.map.get(name);
}

protected Set<String> keySet() {
if (this.map == null) {
return Set.of();
}
return this.map.keySet();
}

protected Enumeration<String> keyEnumeration() {
if (this.map == null) {
return Collections.emptyEnumeration();
}
return Collections.enumeration(this.map.keySet());
}

protected boolean containsKey(String name) {
if (this.map == null) {
return false;
}
return this.map.containsKey(name);
}

protected V put(String name, V value) {
if (this.map == null) {
this.map = concurrent ? new ConcurrentHashMap<>() : new HashMap<>();
}
return this.map.put(name, value);
}

protected V remove(String name) {
if (this.map != null) {
return this.map.remove(name);
}
return null;
}

protected Map<String, V> map() {
if (this.map == null) {
return Map.of();
}
return Collections.unmodifiableMap(this.map);
}
}
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
package engine.support;

import java.util.Enumeration;
import java.util.Map;
import java.util.Objects;

public class Attributes extends LazyMap<Object> {

public Attributes(boolean concurrent) {
super(concurrent);
}

public Attributes() {
this(false);
}

public Object getAttribute(String name) {
Objects.requireNonNull(name, "name is null.");
return super.get(name);
}

public Enumeration<String> getAttributeNames() {
return super.keyEnumeration();
}

public Object setAttribute(String name, Object value) {
Objects.requireNonNull(name, "name is null.");
return super.put(name, value);
}

public Object removeAttribute(String name) {
Objects.requireNonNull(name, "name is null.");
return super.remove(name);
}

public Map<String, Object> getAttributes() {
return super.map();
}
}

在request类中添加属性:

1
Attributes attributes = new Attributes();

实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public Object getAttribute(String name) {
return this.attributes.getAttribute(name);
}

@Override
public Enumeration<String> getAttributeNames() {
return this.attributes.getAttributeNames();
}

@Override
public void setAttribute(String name, Object value) {
if (value == null) {
removeAttribute(name);
} else {
Object oldValue = this.attributes.setAttribute(name, value);
}
}

@Override
public void removeAttribute(String name) {
Object oldValue = this.attributes.removeAttribute(name);
}

涉及session和servletcontent的方法在后续实现相应类时完善。

response必要方法

1
2
3
4
5
6
7
boolean committed = false;

Boolean callOutput = null;

ServletOutputStream output;

PrintWriter writer;

Response 提交后再操作无效,提示异常。

流的关闭。

http响应的固定写法sendResponseHeaders 在写入响应之前。

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
void commitHeaders(long length) throws IOException {
this.httpExchange.sendResponseHeaders(this.status, length);
this.committed = true;
}

public void cleanup() throws IOException {
if (!this.committed) {
commitHeaders(-1);
}
if (this.callOutput != null) {
if (this.callOutput.booleanValue()) {
this.output.close();
} else {
this.writer.close();
}
}
}

// check if not committed:
void checkNotCommitted() {
if (this.committed) {
throw new IllegalStateException("Response is committed.");
}
}

@Override
public boolean isCommitted() {
return this.committed;
}

response添加header相关方法

1
2
3
4
5
6
7
String contentType;

String characterEncoding;

long contentLength = 0;

private Headers respHeaders;
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

@Override
public String getCharacterEncoding() {
return this.characterEncoding;
}

@Override
public String getContentType() {
return this.contentType;
}

@Override
public void setCharacterEncoding(String charset) {
this.characterEncoding = charset;
}

@Override
public void setContentLength(int len) {
this.contentLength = len;
}

@Override
public void setContentLengthLong(long len) {
this.contentLength = len;
}

@Override
public void setContentType(String type) {
this.contentType = type;
if (type.startsWith("text/")) {
setHeader("Content-Type", contentType + "; charset=" + this.characterEncoding);
} else {
setHeader("Content-Type", contentType);
}
}


@Override
public boolean containsHeader(String name) {
return this.respHeaders.containsKey(name);
}

@Override
public Collection<String> getHeaders(String name) {
List<String> hs = this.respHeaders.get(name);
if (hs == null) {
return List.of();
}
return hs;
}

@Override
public String getHeader(String name) {
List<String> hs = this.respHeaders.get(name);
return hs.isEmpty() ? null : hs.get(0);
}

@Override
public Collection<String> getHeaderNames() {
return Collections.unmodifiableSet(this.respHeaders.keySet());
}

@Override
public void setDateHeader(String name, long date) {
checkNotCommitted();
this.respHeaders.set(name, DateUtils.formatDateTimeGMT(date));
}

@Override
public void addDateHeader(String name, long date) {
checkNotCommitted();
this.respHeaders.add(name, DateUtils.formatDateTimeGMT(date));
}

@Override
public void setHeader(String name, String value) {
checkNotCommitted();
this.respHeaders.set(name, value);
}

@Override
public void addHeader(String name, String value) {
checkNotCommitted();
this.respHeaders.add(name, value);
}

@Override
public void setIntHeader(String name, int value) {
checkNotCommitted();
this.respHeaders.set(name, Integer.toString(value));
}

@Override
public void addIntHeader(String name, int value) {
checkNotCommitted();
this.respHeaders.add(name, Integer.toString(value));
}

response添加杂乱的方法

1
2
3
4
5
6
7
private int status = 200;

Locale locale = null;

List<Cookie> cookies = null;

int bufferSize = 1024;
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

@Override
public void setStatus(int i) {
checkNotCommitted();
this.status = i;
}

@Override
public int getStatus() {
return this.status;
}

@Override
public void setLocale(Locale locale) {
checkNotCommitted();
this.locale = locale;
}

@Override
public Locale getLocale() {
return this.locale == null ? Locale.getDefault() : this.locale;
}

@Override
public void addCookie(Cookie cookie) {
checkNotCommitted();
if (this.cookies == null) {
this.cookies = new ArrayList<>();
}
this.cookies.add(cookie);
}


@Override
public String encodeURL(String url) {
// no need to append session id:
return url;
}

@Override
public String encodeRedirectURL(String url) {
// no need to append session id:
return url;
}

@Override
public void setBufferSize(int size) {
if (this.callOutput != null) {
throw new IllegalStateException("Output stream or writer is opened.");
}
if (size < 0) {
throw new IllegalArgumentException("Invalid size: " + size);
}
this.bufferSize = size;
}

@Override
public int getBufferSize() {
return this.bufferSize;
}

@Override
public void flushBuffer() throws IOException {
if (this.callOutput == null) {
throw new IllegalStateException("Output stream or writer is not opened.");
}
if (this.callOutput.booleanValue()) {
this.output.flush();
} else {
this.writer.flush();
}
}

@Override
public void resetBuffer() {
checkNotCommitted();
}


@Override
public void reset() {
checkNotCommitted();
this.status = 200;
this.respHeaders.clear();
}

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
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (callOutput == null) {
commitHeaders(0);
this.output = new ServletOutputStreamImpl(this.httpExchange.getResponseBody());
this.callOutput = Boolean.TRUE;
return this.output;
}
if (callOutput.booleanValue()) {
return this.output;
}
throw new IllegalStateException("Cannot open output stream when writer is opened.");
}

@Override
public PrintWriter getWriter() throws IOException {
if (callOutput == null) {
commitHeaders(0);
this.writer = new PrintWriter(this.httpExchange.getResponseBody(), true, Charset.forName(this.characterEncoding));
this.callOutput = Boolean.FALSE;
return this.writer;
}
if (!callOutput.booleanValue()) {
return this.writer;
}
throw new IllegalStateException("Cannot open writer when output stream is opened.");
}

@Override
public void sendError(int sc, String msg) throws IOException {
checkNotCommitted();
this.status = sc;
commitHeaders(-1);
}

@Override
public void sendError(int sc) throws IOException {
sendError(sc, "Error");
}

@Override
public void sendRedirect(String location) throws IOException {
checkNotCommitted();
this.status = 302;
setHeader("Location", location);
commitHeaders(-1);
}

response构造方法及httpconnector改造

1
2
3
4
5
6
7
8
public HttpServletResponseImpl(HttpExchange httpExchange) {
this.httpExchange = httpExchange;
this.respHeaders = httpExchange.getResponseHeaders();
// 设置默认的字符编码
this.characterEncoding = StandardCharsets.UTF_8.displayName();
// 设置默认响应的内容类型
this.setContentType("text/html");
}
1
2
3
4
5
6
7
8
9
10
@Override
public void handle(HttpExchange exchange) throws IOException {
HttpServletRequestImpl request = new HttpServletRequestImpl(exchange);
HttpServletResponseImpl response = new HttpServletResponseImpl(exchange);
try {
process(request, response);
} finally {
response.cleanup();
}
}

实现ServletContextImpl

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
package engine.support;

import java.util.*;

public class InitParameters extends LazyMap<String> {

public InitParameters() {
super(false);
}

/**
* Sets the initialization parameter with the given name and value on the
* Servlet or Filter that is represented by this Registration.
*
* @param name the initialization parameter name
* @param value the initialization parameter value
*
* @return true if the update was successful, i.e., an initialization parameter
* with the given name did not already exist for the Servlet or Filter
* represented by this Registration, and false otherwise
*
* @throws IllegalArgumentException if the given name or value is <tt>null</tt>
*/
public boolean setInitParameter(String name, String value) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name is null or empty.");
}
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException("Value is null or empty.");
}
if (super.containsKey(name)) {
return false;
}
super.put(name, value);
return true;
}

public String getInitParameter(String name) {
return super.get(name);
}

/**
* Sets the given initialization parameters on the Servlet or Filter that is
* represented by this Registration.
*
* <p>
* The given map of initialization parameters is processed <i>by-value</i>,
* i.e., for each initialization parameter contained in the map, this method
* calls {@link #setInitParameter(String,String)}. If that method would return
* false for any of the initialization parameters in the given map, no updates
* will be performed, and false will be returned. Likewise, if the map contains
* an initialization parameter with a <tt>null</tt> name or value, no updates
* will be performed, and an IllegalArgumentException will be thrown.
*
* <p>
* The returned set is not backed by the {@code Registration} object, so changes
* in the returned set are not reflected in the {@code Registration} object, and
* vice-versa.
* </p>
*
* @param initParameters the initialization parameters
*
* @return the (possibly empty) Set of initialization parameter names that are
* in conflict
*
* @throws IllegalArgumentException if the given map contains an initialization
* parameter with a <tt>null</tt> name or value
*/
public Set<String> setInitParameters(Map<String, String> initParameters) {
if (initParameters == null) {
throw new IllegalArgumentException("initParameters is null.");
}
if (initParameters.isEmpty()) {
return Set.of();
}
Set<String> conflicts = new HashSet<>();
for (String name : initParameters.keySet()) {
String value = initParameters.get(name);
if (value == null) {
throw new IllegalArgumentException("initParameters contains null value by name: " + name);
}
if (super.containsKey(name)) {
conflicts.add(name);
} else {
super.put(name, value);
}
}
return conflicts;
}

public Map<String, String> getInitParameters() {
return super.map();
}

public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(super.map().keySet());
}
}

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package engine;

import engine.support.InitParameters;
import jakarta.servlet.*;

import java.util.*;

public class ServletRegistrationImpl implements ServletRegistration.Dynamic {

final ServletContext servletContext;
final String name;
final Servlet servlet;
final List<String> urlPatterns = new ArrayList<>(4);
final InitParameters initParameters = new InitParameters();

boolean initialized = false;

public ServletRegistrationImpl(ServletContext servletContext, String name, Servlet servlet) {
this.servletContext = servletContext;
this.name = name;
this.servlet = servlet;
}

public ServletConfig getServletConfig() {
return new ServletConfig() {
@Override
public String getServletName() {
return ServletRegistrationImpl.this.name;
}

@Override
public ServletContext getServletContext() {
return ServletRegistrationImpl.this.servletContext;
}

@Override
public String getInitParameter(String name) {
return ServletRegistrationImpl.this.initParameters.getInitParameter(name);
}

@Override
public Enumeration<String> getInitParameterNames() {
return ServletRegistrationImpl.this.initParameters.getInitParameterNames();
}
};
}

@Override
public String getName() {
return this.name;
}

@Override
public String getClassName() {
return servlet.getClass().getName();
}

// proxy to InitParameters:

@Override
public boolean setInitParameter(String name, String value) {
checkNotInitialized("setInitParameter");
return this.initParameters.setInitParameter(name, value);
}

@Override
public String getInitParameter(String name) {
return this.initParameters.getInitParameter(name);
}

@Override
public Set<String> setInitParameters(Map<String, String> initParameters) {
checkNotInitialized("setInitParameter");
return this.initParameters.setInitParameters(initParameters);
}

@Override
public Map<String, String> getInitParameters() {
return this.initParameters.getInitParameters();
}

@Override
public Set<String> addMapping(String... urlPatterns) {
checkNotInitialized("addMapping");
if (urlPatterns == null || urlPatterns.length == 0) {
throw new IllegalArgumentException("Missing urlPatterns.");
}
for (String urlPattern : urlPatterns) {
this.urlPatterns.add(urlPattern);
}
return Set.of();
}

@Override
public Collection<String> getMappings() {
return this.urlPatterns;
}

@Override
public String getRunAsRole() {
return null;
}

@Override
public void setAsyncSupported(boolean isAsyncSupported) {
checkNotInitialized("setAsyncSupported");
if (isAsyncSupported) {
throw new UnsupportedOperationException("Async is not supported.");
}
}

@Override
public void setLoadOnStartup(int loadOnStartup) {
checkNotInitialized("setLoadOnStartup");
// do nothing
}

@Override
public Set<String> setServletSecurity(ServletSecurityElement constraint) {
checkNotInitialized("setServletSecurity");
throw new UnsupportedOperationException("Servlet security is not supported.");
}

@Override
public void setMultipartConfig(MultipartConfigElement multipartConfig) {
checkNotInitialized("setMultipartConfig");
throw new UnsupportedOperationException("Multipart config is not supported.");
}

@Override
public void setRunAsRole(String roleName) {
checkNotInitialized("setRunAsRole");
if (roleName != null) {
throw new UnsupportedOperationException("Role is not supported.");
}
}

private void checkNotInitialized(String name) {
if (this.initialized) {
throw new IllegalStateException("Cannot call " + name + " after initialization.");
}
}
}

addServlet会返回这个接口类,这个类就相当于路径与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
package engine.mapping;

import java.util.regex.Pattern;

public class AbstractMapping implements Comparable<AbstractMapping> {

final Pattern pattern;
final String url;

public AbstractMapping(String urlPattern) {
this.url = urlPattern;
this.pattern = buildPattern(urlPattern);
}

public boolean matches(String uri) {
return pattern.matcher(uri).matches();
}

Pattern buildPattern(String urlPattern) {
StringBuilder sb = new StringBuilder(urlPattern.length() + 16);
sb.append('^');
for (int i = 0; i < urlPattern.length(); i++) {
char ch = urlPattern.charAt(i);
if (ch == '*') {
sb.append(".*");
} else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9') {
sb.append(ch);
} else {
sb.append('\\').append(ch);
}
}
sb.append('$');
return Pattern.compile(sb.toString());
}

@Override
public int compareTo(AbstractMapping o) {
int cmp = this.priority() - o.priority();
if (cmp == 0) {
cmp = this.url.compareTo(o.url);
}
return cmp;
}

int priority() {
if (this.url.equals("/")) {
return Integer.MAX_VALUE;
}
if (this.url.startsWith("*")) {
return Integer.MAX_VALUE - 1;
}
return 100000 - this.url.length();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package engine.mapping;

import jakarta.servlet.Servlet;

public class ServletMapping extends AbstractMapping {

public final Servlet servlet;

public ServletMapping(String urlPattern, Servlet servlet) {
super(urlPattern);
this.servlet = 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
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
package utils;

import jakarta.servlet.DispatcherType;
import jakarta.servlet.Filter;
import jakarta.servlet.Servlet;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;

import java.util.*;
import java.util.stream.Collectors;

public class AnnoUtils {

public static String getServletName(Class<? extends Servlet> clazz) {
WebServlet w = clazz.getAnnotation(WebServlet.class);
if (w != null && !w.name().isEmpty()) {
return w.name();
}
return defaultNameByClass(clazz);
}

public static String getFilterName(Class<? extends Filter> clazz) {
WebFilter w = clazz.getAnnotation(WebFilter.class);
if (w != null && !w.filterName().isEmpty()) {
return w.filterName();
}
return defaultNameByClass(clazz);
}

public static Map<String, String> getServletInitParams(Class<? extends Servlet> clazz) {
WebServlet w = clazz.getAnnotation(WebServlet.class);
if (w == null) {
return Map.of();
}
return initParamsToMap(w.initParams());
}

public static Map<String, String> getFilterInitParams(Class<? extends Filter> clazz) {
WebFilter w = clazz.getAnnotation(WebFilter.class);
if (w == null) {
return Map.of();
}
return initParamsToMap(w.initParams());
}

public static String[] getServletUrlPatterns(Class<? extends Servlet> clazz) {
WebServlet w = clazz.getAnnotation(WebServlet.class);
if (w == null) {
return new String[0];
}
return arraysToSet(w.value(), w.urlPatterns()).toArray(String[]::new);
}

public static String[] getFilterUrlPatterns(Class<? extends Filter> clazz) {
WebFilter w = clazz.getAnnotation(WebFilter.class);
if (w == null) {
return new String[0];
}
return arraysToSet(w.value(), w.urlPatterns()).toArray(String[]::new);
}

public static EnumSet<DispatcherType> getFilterDispatcherTypes(Class<? extends Filter> clazz) {
WebFilter w = clazz.getAnnotation(WebFilter.class);
if (w == null) {
return EnumSet.of(DispatcherType.REQUEST);
}
return EnumSet.copyOf(Arrays.asList(w.dispatcherTypes()));
}

/**
* 首字母小写的类默认名
* @param clazz
* @return
*/
private static String defaultNameByClass(Class<?> clazz) {
String name = clazz.getSimpleName();
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
return name;
}

private static Map<String, String> initParamsToMap(WebInitParam[] params) {
return Arrays.stream(params).collect(Collectors.toMap(p -> p.name(), p -> p.value()));
}

private static Set<String> arraysToSet(String[] arr1) {
Set<String> set = new LinkedHashSet<>();
for (String s : arr1) {
set.add(s);
}
return set;
}

private static Set<String> arraysToSet(String[] arr1, String[] arr2) {
Set<String> set = arraysToSet(arr1);
set.addAll(arraysToSet(arr2));
return set;
}
}

创建ServletContextImpl:

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package engine;

import engine.mapping.ServletMapping;
import jakarta.servlet.*;
import jakarta.servlet.descriptor.JspConfigDescriptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.AnnoUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
* @date : 2024/9/2 10:15
* @description: servlet上下文或容器
*/
public class ServletContextImpl implements ServletContext {

final Logger logger = LoggerFactory.getLogger(getClass());

final List<ServletMapping> servletMappings = new ArrayList<>();

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

private boolean initialized = false;

public void initialize(List<Class<?>> servletClasses) throws ServletException {
// 依次添加每个Servlet:
for (Class<?> c : servletClasses) {
Class<? extends Servlet> clazz = (Class<? extends Servlet>) c;
// 创建一个ServletRegistration.Dynamic:
ServletRegistration.Dynamic registration = this.addServlet(AnnoUtils.getServletName(clazz), clazz);
registration.addMapping(AnnoUtils.getServletUrlPatterns(clazz));
registration.setInitParameters(AnnoUtils.getServletInitParams(clazz));
}
// 实例化Servlet:
for (String name : this.servletRegistrations.keySet()) {
ServletRegistrationImpl registration = this.servletRegistrations.get(name);
registration.servlet.init(registration.getServletConfig());
for (String urlPattern : registration.getMappings()) {
this.servletMappings.add(new ServletMapping(urlPattern, registration.servlet));
}
registration.initialized = true;
}

this.initialized = true;
}

// HTTP请求处理入口:
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 请求路径:
String path = request.getRequestURI();
// 搜索Servlet:
Servlet servlet = null;
for (ServletMapping mapping : this.servletMappings) {
if (mapping.matches(path)) {
// 路径匹配:
servlet = mapping.servlet;
break;
}
}
if (servlet == null) {
// 未匹配到任何Servlet显示404 Not Found:
PrintWriter pw = response.getWriter();
pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
pw.close();
return;
}
// 由Servlet继续处理请求:
servlet.service(request, response);
}

private void checkNotInitialized(String name) {
if (this.initialized) {
throw new IllegalStateException("Cannot call " + name + " after initialization.");
}
}

private <T> T createInstance(String className) throws ServletException {
Class<T> clazz;
try {
clazz = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Class not found.", e);
}
return createInstance(clazz);
}

private <T> T createInstance(Class<T> clazz) throws ServletException {
try {
Constructor<T> constructor = clazz.getConstructor();
return constructor.newInstance();
} catch (ReflectiveOperationException e) {
throw new ServletException("Cannot instantiate class " + clazz.getName(), e);
}
}

@Override
public ServletRegistration.Dynamic addServlet(String name, String className) {
checkNotInitialized("addServlet");
if (className == null || className.isEmpty()) {
throw new IllegalArgumentException("class name is null or empty.");
}
Servlet servlet = null;
try {
Class<? extends Servlet> clazz = createInstance(className);
servlet = createInstance(clazz);
} catch (ServletException e) {
throw new RuntimeException(e);
}
return addServlet(name, servlet);
}

@Override
public ServletRegistration.Dynamic addServlet(String name, Class<? extends Servlet> clazz) {
checkNotInitialized("addServlet");
if (clazz == null) {
throw new IllegalArgumentException("class is null.");
}
Servlet servlet = null;
try {
servlet = createInstance(clazz);
} catch (ServletException e) {
throw new RuntimeException(e);
}
return addServlet(name, servlet);
}

@Override
public ServletRegistration.Dynamic addServlet(String name, Servlet servlet) {
checkNotInitialized("addServlet");
if (name == null) {
throw new IllegalArgumentException("name is null.");
}
if (servlet == null) {
throw new IllegalArgumentException("servlet is null.");
}
var registration = new ServletRegistrationImpl(this, name, servlet);
this.servletRegistrations.put(name, registration);
return registration;
}
}

添加测试的servlet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package engine.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String html = "<h1>Index Page</h1>";
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write(html);
pw.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package engine.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String html = "<h1>Hello, " + (name == null ? "world" : name) + ".</h1>";
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write(html);
pw.close();
}
}

httpConnector中添加servletcontext:

1
2
// 持有ServletContext实例:
final ServletContextImpl servletContext;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public HttpConnector( String host, int port) throws IOException, ServletException {
this.host = host;
this.port = port;
this.httpServer = HttpServer.create(new InetSocketAddress(this.host, this.port), 0);
this.httpServer.createContext("/", this);
this.httpServer.start();
this.servletContext = new ServletContextImpl();
servletContext.initialize(List.of(IndexServlet.class, HelloServlet.class));
}

@Override
public void handle(HttpExchange exchange) throws IOException {
HttpServletRequestImpl request = new HttpServletRequestImpl(exchange);
HttpServletResponseImpl response = new HttpServletResponseImpl(exchange);
try {
servletContext.process(request, response);
} catch (ServletException e) {
logger.error(e.getMessage(), e);
} finally {
response.cleanup();
}
}

完善requestImpl的sevletcontext相关方法

1
2
3
    final ServletContextImpl servletContext;
// 构造方法传入
this.servletContext = servletContext;
1
2
3
4
5
6
7
8
9
@Override
public String getPathTranslated() {
return this.servletContext.getRealPath(getRequestURI());
}

@Override
public ServletContext getServletContext() {
return this.servletContext;
}

实现filterChain

Servlet可以被一个或多个Filter按照一定的顺序组成一个处理链(FilterChain),用来处理一些公共逻辑,比如打印日志、登录检查等。

FilterChain是一个责任链模式。

同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
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
119
120
121
122
123
124
package engine;

import engine.support.InitParameters;
import jakarta.servlet.*;

import java.util.*;

public class FilterRegistrationImpl implements FilterRegistration.Dynamic {

final ServletContext servletContext;
final String name;
final Filter filter;

final InitParameters initParameters = new InitParameters();
final List<String> urlPatterns = new ArrayList<>(4);
boolean initialized = false;

public FilterRegistrationImpl(ServletContext servletContext, String name, Filter filter) {
this.servletContext = servletContext;
this.name = name;
this.filter = filter;
}

public FilterConfig getFilterConfig() {
return new FilterConfig() {
@Override
public String getFilterName() {
return FilterRegistrationImpl.this.name;
}

@Override
public ServletContext getServletContext() {
return FilterRegistrationImpl.this.servletContext;
}

@Override
public String getInitParameter(String name) {
return FilterRegistrationImpl.this.initParameters.getInitParameter(name);
}

@Override
public Enumeration<String> getInitParameterNames() {
return FilterRegistrationImpl.this.initParameters.getInitParameterNames();
}
};
}

@Override
public String getName() {
return this.name;
}

@Override
public String getClassName() {
return filter.getClass().getName();
}

// proxy to InitParameters:

@Override
public boolean setInitParameter(String name, String value) {
checkNotInitialized("setInitParameter");
return this.initParameters.setInitParameter(name, value);
}

@Override
public String getInitParameter(String name) {
return this.initParameters.getInitParameter(name);
}

@Override
public Set<String> setInitParameters(Map<String, String> initParameters) {
checkNotInitialized("setInitParameter");
return this.initParameters.setInitParameters(initParameters);
}

@Override
public Map<String, String> getInitParameters() {
return this.initParameters.getInitParameters();
}

@Override
public void setAsyncSupported(boolean isAsyncSupported) {
checkNotInitialized("setInitParameter");
if (isAsyncSupported) {
throw new UnsupportedOperationException("Async is not supported.");
}
}

@Override
public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames) {
throw new UnsupportedOperationException("addMappingForServletNames");
}

@Override
public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) {
checkNotInitialized("addMappingForUrlPatterns");
if (!dispatcherTypes.contains(DispatcherType.REQUEST) || dispatcherTypes.size() != 1) {
throw new IllegalArgumentException("Only support DispatcherType.REQUEST.");
}
if (urlPatterns == null || urlPatterns.length == 0) {
throw new IllegalArgumentException("Missing urlPatterns.");
}
for (String urlPattern : urlPatterns) {
this.urlPatterns.add(urlPattern);
}
}

@Override
public Collection<String> getServletNameMappings() {
return List.of();
}

@Override
public Collection<String> getUrlPatternMappings() {
return this.urlPatterns;
}

private void checkNotInitialized(String name) {
if (this.initialized) {
throw new IllegalStateException("Cannot call " + name + " after initialization.");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package engine.mapping;

import jakarta.servlet.Filter;

public class FilterMapping extends AbstractMapping {

public final Filter filter;

public FilterMapping(String urlPattern, Filter filter) {
super(urlPattern);
this.filter = filter;
}
}
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
package engine;

import jakarta.servlet.*;

import java.io.IOException;

public class FilterChainImpl implements FilterChain {

final Filter[] filters;
final Servlet servlet;
final int total;
int index = 0;

public FilterChainImpl(Filter[] filters, Servlet servlet) {
this.filters = filters;
this.servlet = servlet;
this.total = filters.length;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (index < total) {
int current = index;
index++;
filters[current].doFilter(request, response, this);
} else {
servlet.service(request, response);
}
}
}

servletContext:

1
2
3
private Map<String, FilterRegistrationImpl> filterRegistrations = new HashMap<>();

final List<FilterMapping> filterMappings = new ArrayList<>();
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
public void initFilters(List<Class<?>> filterClasses) {
for (Class<?> c : filterClasses) {
WebFilter wf = c.getAnnotation(WebFilter.class);
if (wf != null) {
logger.info("auto register @WebFilter: {}", c.getName());
@SuppressWarnings("unchecked")
Class<? extends Filter> clazz = (Class<? extends Filter>) c;
FilterRegistration.Dynamic registration = this.addFilter(AnnoUtils.getFilterName(clazz), clazz);
registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, AnnoUtils.getFilterUrlPatterns(clazz));
registration.setInitParameters(AnnoUtils.getFilterInitParams(clazz));
}
}

// init filters:
for (String name : this.filterRegistrations.keySet()) {
var registration = this.filterRegistrations.get(name);
try {
registration.filter.init(registration.getFilterConfig());
for (String urlPattern : registration.getUrlPatternMappings()) {
this.filterMappings.add(new FilterMapping(urlPattern, registration.filter));
}
registration.initialized = true;
} catch (ServletException e) {
logger.error("init filter failed: " + name + " / " + registration.filter.getClass().getName(), e);
}
}
this.initialized = true;
}

@Override
public FilterRegistration.Dynamic addFilter(String name, String className) {
checkNotInitialized("addFilter");
if (className == null || className.isEmpty()) {
throw new IllegalArgumentException("class name is null or empty.");
}
Filter filter = null;
try {
Class<? extends Filter> clazz = createInstance(className);
filter = createInstance(clazz);
} catch (ServletException e) {
throw new RuntimeException(e);
}
return addFilter(name, filter);
}

@Override
public FilterRegistration.Dynamic addFilter(String name, Class<? extends Filter> clazz) {
checkNotInitialized("addFilter");
if (clazz == null) {
throw new IllegalArgumentException("class is null.");
}
Filter filter = null;
try {
filter = createInstance(clazz);
} catch (ServletException e) {
throw new RuntimeException(e);
}
return addFilter(name, filter);
}

@Override
public FilterRegistration.Dynamic addFilter(String name, Filter filter) {
checkNotInitialized("addFilter");
if (name == null) {
throw new IllegalArgumentException("name is null.");
}
if (filter == null) {
throw new IllegalArgumentException("filter is null.");
}
var registration = new FilterRegistrationImpl(this, name, filter);
this.filterRegistrations.put(name, registration);
return registration;
}

@Override
public <T extends Filter> T createFilter(Class<T> clazz) throws ServletException {
checkNotInitialized("createFilter");
return createInstance(clazz);
}
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
// HTTP请求处理入口:
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// 请求路径:
String path = request.getRequestURI();
// 搜索Servlet:
Servlet servlet = null;
for (ServletMapping mapping : this.servletMappings) {
if (mapping.matches(path)) {
// 路径匹配:
servlet = mapping.servlet;
break;
}
}
if (servlet == null) {
// 未匹配到任何Servlet显示404 Not Found:
PrintWriter pw = response.getWriter();
pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + path + "</p>");
pw.close();
return;
}
// search filter:
List<Filter> enabledFilters = new ArrayList<>();
for (FilterMapping mapping : this.filterMappings) {
if (mapping.matches(path)) {
enabledFilters.add(mapping.filter);
}
}
Filter[] filters = enabledFilters.toArray(Filter[]::new);
logger.atDebug().log("process {} by filter {}, servlet {}", path, Arrays.toString(filters), servlet);
FilterChain chain = new FilterChainImpl(filters, servlet);

try {
chain.doFilter(request, response);
} catch (ServletException e) {
logger.error(e.getMessage(), e);
throw new IOException(e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
throw e;
}
}

添加测试filter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package engine.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {

final Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
logger.info("{}: {}", req.getMethod(), req.getRequestURI());
chain.doFilter(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
package engine.filter;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Set;

@WebFilter(urlPatterns = "/hello")
public class HelloFilter implements Filter {

final Logger logger = LoggerFactory.getLogger(getClass());

Set<String> names = Set.of("Bob", "Alice", "Tom", "Jerry");

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String name = req.getParameter("name");
logger.info("Check parameter name = {}", name);
if (name != null && names.contains(name)) {
chain.doFilter(request, response);
} else {
logger.warn("Access denied: name = {}", name);
HttpServletResponse resp = (HttpServletResponse) response;
resp.sendError(403, "Forbidden");
}
}
}
1
servletContext.initFilters(List.of(LogFilter.class, HelloFilter.class));

实现HttpSession

实现httpSession接口:

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
119
120
package engine;

import engine.support.Attributes;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpSession;

import java.util.Enumeration;

/**
* @author : sz-hanqc@chinaunicom.cn
* @date : 2024/9/4 09:27
* @description: httpsession实现类
*/
public class HttpSessionImpl implements HttpSession {

final ServletContextImpl servletContext;

String sessionId;

int maxInactiveInterval;

long creationTime;

long lastAccessedTime;

Attributes attributes;

public HttpSessionImpl(ServletContextImpl servletContext, String sessionId, int interval) {
this.servletContext = servletContext;
this.sessionId = sessionId;
this.creationTime = this.lastAccessedTime = System.currentTimeMillis();
this.attributes = new Attributes(true);
setMaxInactiveInterval(interval);
}

void checkValid() {
if (this.sessionId == null) {
throw new IllegalStateException("Session is already invalidated.");
}
}

@Override
public String toString() {
return String.format("HttpSessionImpl@%s[id=%s]", Integer.toHexString(hashCode()), this.getId());
}

@Override
public long getCreationTime() {
return creationTime;
}

@Override
public String getId() {
return this.sessionId;
}

@Override
public long getLastAccessedTime() {
return this.lastAccessedTime;
}

@Override
public ServletContext getServletContext() {
return this.servletContext;
}

@Override
public void setMaxInactiveInterval(int interval) {
this.maxInactiveInterval = interval;

}

@Override
public int getMaxInactiveInterval() {
return this.maxInactiveInterval;
}



@Override
public boolean isNew() {
return this.creationTime == this.lastAccessedTime;
}

// attribute operations ///////////////////////////////////////////////////

@Override
public Object getAttribute(String name) {
checkValid();
return this.attributes.getAttribute(name);
}

@Override
public Enumeration<String> getAttributeNames() {
checkValid();
return this.attributes.getAttributeNames();
}

@Override
public void setAttribute(String name, Object value) {
checkValid();
if (value == null) {
removeAttribute(name);
} else {
Object oldValue = this.attributes.setAttribute(name, value);
}
}

@Override
public void removeAttribute(String name) {
checkValid();
Object oldValue = this.attributes.removeAttribute(name);
}


@Override
public void invalidate() {

}
}

其中invalidate方法是使session变为无效。前端传cookies中jsessionId至服务器端,服务器端根据jsessionId从内存中获取到session对象。所以需要有一个相当于map的对象。

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
package engine;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import jakarta.servlet.http.HttpSession;
import utils.DateUtils;

public class SessionManager implements Runnable {

final Logger logger = LoggerFactory.getLogger(getClass());

final ServletContextImpl servletContext;
final Map<String, HttpSessionImpl> sessions = new ConcurrentHashMap<>();
int inactiveInterval;

public SessionManager(ServletContextImpl servletContext, int interval) {
this.servletContext = servletContext;
this.inactiveInterval = interval;
Thread t = new Thread(this, "Session-Cleanup-Thread");
t.setDaemon(true);
t.start();
}

public void setInactiveInterval(int inactiveInterval) {
this.inactiveInterval = inactiveInterval;
}

public HttpSession getSession(String sessionId) {
HttpSessionImpl session = sessions.get(sessionId);
if (session == null) {
session = new HttpSessionImpl(this.servletContext, sessionId, inactiveInterval);
sessions.put(sessionId, session);
} else {
session.lastAccessedTime = System.currentTimeMillis();
}
return session;
}

public void remove(HttpSession session) {
this.sessions.remove(session.getId());
}

@Override
public void run() {
for (;;) {
try {
Thread.sleep(60_000L);
} catch (InterruptedException e) {
break;
}
long now = System.currentTimeMillis();
for (String sessionId : sessions.keySet()) {
HttpSession session = sessions.get(sessionId);
if (session.getLastAccessedTime() + session.getMaxInactiveInterval() * 1000L < now) {
logger.atDebug().log("remove expired session: {}, last access time: {}", sessionId,
DateUtils.formatDateTimeGMT(session.getLastAccessedTime()));
session.invalidate();
}
}
}
}
}

创建了sessionmanager类,在哪里实例化并交给谁呢,sessionmanager是随着容器启动而实例化,销毁而销毁的,交给servletContext。

在sevletContext中添加属性,在构造方法中实例化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final SessionManager sessionManager;

int sessionTimeout = 1800;


this.sessionManager = new SessionManager(this, sessionTimeout);

@Override
public int getSessionTimeout() {
return this.sessionTimeout;
}

@Override
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
this.sessionManager.setInactiveInterval(sessionTimeout);
}

完善httpsession实现类:

1
2
3
4
5
6
@Override
public void invalidate() {
checkValid();
this.servletContext.sessionManager.remove(this);
this.sessionId = null;
}

完善request实现类的session相关方法:

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
final HttpServletResponse response;
// 构造方法
this.response = response;

@Override
public HttpSession getSession(boolean create) {
String sessionId = null;
Cookie[] cookies = getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("JSESSIONID".equals(cookie.getName())) {
sessionId = cookie.getValue();
break;
}
}
}
if (sessionId == null && !create) {
return null;
}
if (sessionId == null) {
if (this.response.isCommitted()) {
throw new IllegalStateException("Cannot create session for response is commited.");
}
sessionId = UUID.randomUUID().toString();
// set cookie:
String cookieValue = "JSESSIONID" + "=" + sessionId + "; Path=/; SameSite=Strict; HttpOnly";
this.response.addHeader("Set-Cookie", cookieValue);
}
return this.servletContext.sessionManager.getSession(sessionId);
}

@Override
public HttpSession getSession() {
return getSession(true);
}

创建测试类:

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
@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
String username = (String) session.getAttribute("username");
String html;
if (username == null) {
html = """
<h1>Index Page</h1>
<form method="post" action="/login">
<legend>Please Login</legend>
<p>User Name: <input type="text" name="username"></p>
<p>Password: <input type="password" name="password"></p>
<p><button type="submit">Login</button></p>
</form>
""";
} else {
html = """
<h1>Index Page</h1>
<p>Welcome, {username}!</p>
<p><a href="/logout">Logout</a></p>
""".replace("{username}", username);
}
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write(html);
pw.close();
}
}
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
@WebServlet(urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
Map<String, String> users = Map.of( // user database
"bob", "bob123", //
"alice", "alice123", //
"root", "admin123" //
);

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
String expectedPassword = users.get(username.toLowerCase());
if (expectedPassword == null || !expectedPassword.equals(password)) {
PrintWriter pw = resp.getWriter();
pw.write("""
<h1>Login Failed</h1>
<p>Invalid username or password.</p>
<p><a href="/">Try again</a></p>
""");
pw.close();
} else {
req.getSession().setAttribute("username", username);
resp.sendRedirect("/");
}
}

}
1
2
3
4
5
6
7
8
9
10
11
@WebServlet(urlPatterns = "/logout")
public class LogoutServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
session.invalidate();
resp.sendRedirect("/");
}

}

实现Listener

Listener机制是基于观察者模式实现的,即当某个事件发生时,Listener会接收到通知并执行相应的操作。

在servletContext添加:

1
2
3
4
5
6
private List<ServletContextListener> servletContextListeners = null;
private List<ServletContextAttributeListener> servletContextAttributeListeners = null;
private List<ServletRequestListener> servletRequestListeners = null;
private List<ServletRequestAttributeListener> servletRequestAttributeListeners = null;
private List<HttpSessionAttributeListener> httpSessionAttributeListeners = null;
private List<HttpSessionListener> httpSessionListeners = null;
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

@Override
public void addListener(String className) {
checkNotInitialized("addListener");
if (className == null || className.isEmpty()) {
throw new IllegalArgumentException("class name is null or empty.");
}
EventListener listener = null;
try {
Class<EventListener> clazz = createInstance(className);
listener = createInstance(clazz);
} catch (ServletException e) {
throw new RuntimeException(e);
}
addListener(listener);
}

@Override
public void addListener(Class<? extends EventListener> clazz) {
checkNotInitialized("addListener");
if (clazz == null) {
throw new IllegalArgumentException("class is null.");
}
EventListener listener = null;
try {
listener = createInstance(clazz);
} catch (ServletException e) {
throw new RuntimeException(e);
}
addListener(listener);
}

@Override
public <T extends EventListener> void addListener(T t) {
checkNotInitialized("addListener");
if (t == null) {
throw new IllegalArgumentException("listener is null.");
}
if (t instanceof ServletContextListener listener) {
if (this.servletContextListeners == null) {
this.servletContextListeners = new ArrayList<>();
}
this.servletContextListeners.add(listener);
} else if (t instanceof ServletContextAttributeListener listener) {
if (this.servletContextAttributeListeners == null) {
this.servletContextAttributeListeners = new ArrayList<>();
}
this.servletContextAttributeListeners.add(listener);
} else if (t instanceof ServletRequestListener listener) {
if (this.servletRequestListeners == null) {
this.servletRequestListeners = new ArrayList<>();
}
this.servletRequestListeners.add(listener);
} else if (t instanceof ServletRequestAttributeListener listener) {
if (this.servletRequestAttributeListeners == null) {
this.servletRequestAttributeListeners = new ArrayList<>();
}
this.servletRequestAttributeListeners.add(listener);
} else if (t instanceof HttpSessionAttributeListener listener) {
if (this.httpSessionAttributeListeners == null) {
this.httpSessionAttributeListeners = new ArrayList<>();
}
this.httpSessionAttributeListeners.add(listener);
} else if (t instanceof HttpSessionListener listener) {
if (this.httpSessionListeners == null) {
this.httpSessionListeners = new ArrayList<>();
}
this.httpSessionListeners.add(listener);
} else {
throw new IllegalArgumentException("Unsupported listener: " + t.getClass().getName());
}
}
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

void invokeServletContextInitialized() {
logger.debug("invoke ServletContextInitialized: {}", this);
if (this.servletContextListeners != null) {
var event = new ServletContextEvent(this);
for (var listener : this.servletContextListeners) {
listener.contextInitialized(event);
}
}
}

void invokeServletContextDestroyed() {
logger.debug("invoke ServletContextDestroyed: {}", this);
if (this.servletContextListeners != null) {
var event = new ServletContextEvent(this);
for (var listener : this.servletContextListeners) {
listener.contextDestroyed(event);
}
}
}

void invokeServletContextAttributeAdded(String name, Object value) {
logger.debug("invoke ServletContextAttributeAdded: {} = {}", name, value);
if (this.servletContextAttributeListeners != null) {
var event = new ServletContextAttributeEvent(this, name, value);
for (var listener : this.servletContextAttributeListeners) {
listener.attributeAdded(event);
}
}
}

void invokeServletContextAttributeRemoved(String name, Object value) {
logger.debug("invoke ServletContextAttributeRemoved: {} = {}", name, value);
if (this.servletContextAttributeListeners != null) {
var event = new ServletContextAttributeEvent(this, name, value);
for (var listener : this.servletContextAttributeListeners) {
listener.attributeRemoved(event);
}
}
}

void invokeServletContextAttributeReplaced(String name, Object value) {
logger.debug("invoke ServletContextAttributeReplaced: {} = {}", name, value);
if (this.servletContextAttributeListeners != null) {
var event = new ServletContextAttributeEvent(this, name, value);
for (var listener : this.servletContextAttributeListeners) {
listener.attributeReplaced(event);
}
}
}

void invokeServletRequestAttributeAdded(HttpServletRequest request, String name, Object value) {
logger.debug("invoke ServletRequestAttributeAdded: {} = {}, request = {}", name, value, request);
if (this.servletRequestAttributeListeners != null) {
var event = new ServletRequestAttributeEvent(this, request, name, value);
for (var listener : this.servletRequestAttributeListeners) {
listener.attributeAdded(event);
}
}
}

void invokeServletRequestAttributeRemoved(HttpServletRequest request, String name, Object value) {
logger.debug("invoke ServletRequestAttributeRemoved: {} = {}, request = {}", name, value, request);
if (this.servletRequestAttributeListeners != null) {
var event = new ServletRequestAttributeEvent(this, request, name, value);
for (var listener : this.servletRequestAttributeListeners) {
listener.attributeRemoved(event);
}
}
}

void invokeServletRequestAttributeReplaced(HttpServletRequest request, String name, Object value) {
logger.debug("invoke ServletRequestAttributeReplaced: {} = {}, request = {}", name, value, request);
if (this.servletRequestAttributeListeners != null) {
var event = new ServletRequestAttributeEvent(this, request, name, value);
for (var listener : this.servletRequestAttributeListeners) {
listener.attributeReplaced(event);
}
}
}

void invokeHttpSessionAttributeAdded(HttpSession session, String name, Object value) {
logger.debug("invoke HttpSessionAttributeAdded: {} = {}, session = {}", name, value, session);
if (this.httpSessionAttributeListeners != null) {
var event = new HttpSessionBindingEvent(session, name, value);
for (var listener : this.httpSessionAttributeListeners) {
listener.attributeAdded(event);
}
}
}

void invokeHttpSessionAttributeRemoved(HttpSession session, String name, Object value) {
logger.debug("invoke ServletContextAttributeRemoved: {} = {}, session = {}", name, value, session);
if (this.httpSessionAttributeListeners != null) {
var event = new HttpSessionBindingEvent(session, name, value);
for (var listener : this.httpSessionAttributeListeners) {
listener.attributeRemoved(event);
}
}
}

void invokeHttpSessionAttributeReplaced(HttpSession session, String name, Object value) {
logger.debug("invoke ServletContextAttributeReplaced: {} = {}, session = {}", name, value, session);
if (this.httpSessionAttributeListeners != null) {
var event = new HttpSessionBindingEvent(session, name, value);
for (var listener : this.httpSessionAttributeListeners) {
listener.attributeReplaced(event);
}
}
}

void invokeServletRequestInitialized(HttpServletRequest request) {
logger.debug("invoke ServletRequestInitialized: request = {}", request);
if (this.servletRequestListeners != null) {
var event = new ServletRequestEvent(this, request);
for (var listener : this.servletRequestListeners) {
listener.requestInitialized(event);
}
}
}

void invokeServletRequestDestroyed(HttpServletRequest request) {
logger.debug("invoke ServletRequestDestroyed: request = {}", request);
if (this.servletRequestListeners != null) {
var event = new ServletRequestEvent(this, request);
for (var listener : this.servletRequestListeners) {
listener.requestDestroyed(event);
}
}
}

void invokeHttpSessionCreated(HttpSession session) {
logger.debug("invoke HttpSessionCreated: session = {}", session);
if (this.httpSessionListeners != null) {
var event = new HttpSessionEvent(session);
for (var listener : this.httpSessionListeners) {
listener.sessionCreated(event);
}
}
}

void invokeHttpSessionDestroyed(HttpSession session) {
logger.debug("invoke HttpSessionDestroyed: session = {}", session);
if (this.httpSessionListeners != null) {
var event = new HttpSessionEvent(session);
for (var listener : this.httpSessionListeners) {
listener.sessionDestroyed(event);
}
}
}

添加servletcontent中attribute触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void setAttribute(String name, Object value) {
if (value == null) {
removeAttribute(name);
} else {
Object old = this.attributes.setAttribute(name, value);
if (old == null) {
this.invokeServletContextAttributeAdded(name, value);
} else {
this.invokeServletContextAttributeReplaced(name, value);
}
}
}

@Override
public void removeAttribute(String name) {
Object old = this.attributes.removeAttribute(name);
this.invokeServletContextAttributeRemoved(name, old);
}

添加request中attribute触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void setAttribute(String name, Object value) {
if (value == null) {
removeAttribute(name);
} else {
Object oldValue = this.attributes.setAttribute(name, value);
if (oldValue == null) {
this.servletContext.invokeServletRequestAttributeAdded(this, name, value);
} else {
this.servletContext.invokeServletRequestAttributeReplaced(this, name, value);
}
}
}

@Override
public void removeAttribute(String name) {
Object oldValue = this.attributes.removeAttribute(name);
this.servletContext.invokeServletRequestAttributeRemoved(this, name, oldValue);
}

session触发:sessionManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public HttpSession getSession(String sessionId) {
HttpSessionImpl session = sessions.get(sessionId);
if (session == null) {
session = new HttpSessionImpl(this.servletContext, sessionId, inactiveInterval);
sessions.put(sessionId, session);
this.servletContext.invokeHttpSessionCreated(session);
} else {
session.lastAccessedTime = System.currentTimeMillis();
}
return session;
}

public void remove(HttpSession session) {
this.sessions.remove(session.getId());
this.servletContext.invokeHttpSessionDestroyed(session);
}

session的attribute触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void setAttribute(String name, Object value) {
checkValid();
if (value == null) {
removeAttribute(name);
} else {
Object oldValue = this.attributes.setAttribute(name, value);
if (oldValue == null) {
this.servletContext.invokeHttpSessionAttributeAdded(this, name, value);
} else {
this.servletContext.invokeHttpSessionAttributeReplaced(this, name, value);
}
}
}

@Override
public void removeAttribute(String name) {
checkValid();
Object oldValue = this.attributes.removeAttribute(name);
this.servletContext.invokeHttpSessionAttributeRemoved(this, name, oldValue);
}

servletContext触发:

这个需要在加载完lisenter类名执行。

1
2
3
4
5
6
7
8
9
10
// 自定义初始化方法
initialize(){
// 加载完lisenter之后
this.invokeServletContextInitialized();
}
// 自定义销毁方法
destroy(){
// 销毁servlet 和 filter之后
this.invokeServletContextInitialized();
}
1
2
3
4
5
6
httpconnector
@Override
public void close() throws Exception {
this.servletContext.destroy();
this.httpServer.stop(3);
}

request触发:

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
public void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
String path = request.getRequestURI();
// search servlet:
Servlet servlet = null;
for (ServletMapping mapping : this.servletMappings) {
if (mapping.matches(path)) {
servlet = mapping.servlet;
break;
}
}

if (servlet == null) {
// 404 Not Found:
PrintWriter pw = response.getWriter();
pw.write("<h1>404 Not Found</h1><p>No mapping for URL: " + HtmlUtils.encodeHtml(path) + "</p>");
pw.flush();
return;
}
// search filter:
List<Filter> enabledFilters = new ArrayList<>();
for (FilterMapping mapping : this.filterMappings) {
if (mapping.matches(path)) {
enabledFilters.add(mapping.filter);
}
}
Filter[] filters = enabledFilters.toArray(Filter[]::new);
logger.atDebug().log("process {} by filter {}, servlet {}", path, Arrays.toString(filters), servlet);
FilterChain chain = new FilterChainImpl(filters, servlet);

try {
this.invokeServletRequestInitialized(request);
chain.doFilter(request, response);
} catch (ServletException e) {
logger.error(e.getMessage(), e);
throw new IOException(e);
} catch (IOException e) {
logger.error(e.getMessage(), e);
throw e;
} finally {
this.invokeServletRequestDestroyed(request);
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@WebListener
public class HelloHttpSessionListener implements HttpSessionListener {

final Logger logger = LoggerFactory.getLogger(getClass());

@Override
public void sessionCreated(HttpSessionEvent se) {
logger.info(">>> HttpSession created: {}", se.getSession());
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
logger.info(">>> HttpSession destroyed: {}", se.getSession());
}
}

Httpconnector添加:

1
2
3
4
List<Class<? extends EventListener>> listenerClasses = List.of(HelloHttpSessionListener.class);
for (Class<? extends EventListener> listenerClass : listenerClasses) {
this.servletContext.addListener(listenerClass);
}

完善servlet相关的类

用到哪里改哪里吧

加载web app

如何加载webapp ?

当使用 java -jar xx.jar启动项目,怎么把我需要运行的web app 传给 mytomcat呢。

把war传给项目后,项目会进行解压。

然后进行加载类,这时需要我们自定义类加载器。因为classpath 无法动态的改变,所以无法给BootClassLoader、PlatformClassLoader、AppClassLoader。(如果把解压内容手动放到这三个类加载器,还会造成jar的依赖混乱。)

接收参数:

1
2
3
4
5
6
    <commons-cli.version>1.5.0</commons-cli.version>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>${commons-cli.version}</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws URISyntaxException {
String warFile = null;
String customConfigPath = null;
Options options = new Options();
options.addOption(Option.builder("w").longOpt("war").argName("file").hasArg().desc("specify war file.").required().build());
options.addOption(Option.builder("c").longOpt("config").argName("file").hasArg().desc("specify external configuration file.").build());
try {
var parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
warFile = cmd.getOptionValue("war");
customConfigPath = cmd.getOptionValue("config");
} catch (ParseException e) {
System.err.println(e.getMessage());
var help = new HelpFormatter();
var jarname = Path.of(Start.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getFileName().toString();
help.printHelp("java -jar " + jarname + " [options]", options);
System.exit(1);
return;
}
}
1
java -jar mytomcat.jar -w xxx.war

解压war:

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
/**
* 返回path , 如果不存在退出
* @param warFile
* @return
*/
Path parseWarFile(String warFile) {
Path warPath = Path.of(warFile).toAbsolutePath().normalize();
if (!Files.isRegularFile(warPath) && !Files.isDirectory(warPath)) {
System.err.printf("war file '%s' was not found.\n", warFile);
System.exit(1);
}
return warPath;
}

/**
* 删除目录或文件
* @param p
* @throws IOException
*/
void deleteDir(Path p) throws IOException {
Files.list(p).forEach(c -> {
try {
if (Files.isDirectory(c)) {
deleteDir(c);
} else {
Files.delete(c);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
Files.delete(p);
}

/**
* 创建临时目录
* @return
* @throws IOException
*/
Path createExtractTo() throws IOException {
Path tmp = Files.createTempDirectory("_jm_");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
deleteDir(tmp);
} catch (IOException e) {
e.printStackTrace();
}
}));
return tmp;
}

/**
* 返回运行项目的classes path 和 lib path
* @param warPath
* @return
* @throws IOException
*/
Path[] extractWarIfNecessary(Path warPath) throws IOException {
if (Files.isDirectory(warPath)) {
logger.info("war is directy: {}", warPath);
Path classesPath = warPath.resolve("WEB-INF/classes");
Path libPath = warPath.resolve("WEB-INF/lib");
Files.createDirectories(classesPath);
Files.createDirectories(libPath);
return new Path[] { classesPath, libPath };
}
Path extractPath = createExtractTo();
logger.info("extract '{}' to '{}'", warPath, extractPath);
JarFile war = new JarFile(warPath.toFile());
war.stream().sorted((e1, e2) -> e1.getName().compareTo(e2.getName())).forEach(entry -> {
if (!entry.isDirectory()) {
Path file = extractPath.resolve(entry.getName());
Path dir = file.getParent();
if (!Files.isDirectory(dir)) {
try {
Files.createDirectories(dir);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
try (InputStream in = war.getInputStream(entry)) {
Files.copy(in, file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
});
// check WEB-INF/classes and WEB-INF/lib:
Path classesPath = extractPath.resolve("WEB-INF/classes");
Path libPath = extractPath.resolve("WEB-INF/lib");
Files.createDirectories(classesPath);
Files.createDirectories(libPath);
return new Path[] { classesPath, libPath };
}
1
2
3
4
5
6
7
8
9
10
public void start(String warFile, String customConfigPath) throws IOException {
Path warPath = parseWarFile(warFile);

// extract war if necessary:
Path[] ps = extractWarIfNecessary(warPath);

String webRoot = ps[0].getParent().getParent().toString();
logger.info("set web root: {}", webRoot);

}

我们找到了类的目录后,需要加载webapp的selvlet 和 filter 和 lisenter,所以需要一个自定义的类加载器。

1
2
3
public record Resource(Path path, String name) {  

}
1
record 是Java 14中引入的一个预览特性(preview feature),并在Java 16中作为正式特性发布。它提供了一种简洁的方式来声明不可变的类,主要用于表示数据聚合(即只包含数据而不包含逻辑的类)。使用record可以大大简化代码,因为它自动生成了许多样板代码,比如构造器、equals()、hashCode()和toString()方法等。
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
public class WebAppClassLoader extends URLClassLoader {

final Logger logger = LoggerFactory.getLogger(getClass());

public WebAppClassLoader(Path classPath, Path libPath) throws IOException {
super("WebAppClassLoader", createUrls(classPath, libPath), ClassLoader.getSystemClassLoader());
}

static URL[] createUrls(Path classPath, Path libPath) throws IOException {
List<URL> urls = new ArrayList<>();
urls.add(toDirURL(classPath));
Files.list(libPath).filter(p -> p.toString().endsWith(".jar")).sorted().forEach(p -> {
urls.add(toJarURL(p));
});
return urls.toArray(URL[]::new);
}

static URL toDirURL(Path p) {
try {
if (Files.isDirectory(p)) {
String abs = toAbsPath(p);
if (!abs.endsWith("/")) {
abs = abs + "/";
}
return URI.create("file://" + abs).toURL();
}
throw new IOException("Path is not a directory: " + p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

static URL toJarURL(Path p) {
try {
if (Files.isRegularFile(p)) {
String abs = toAbsPath(p);
return URI.create("file://" + abs).toURL();
}
throw new IOException("Path is not a jar file: " + p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

static String toAbsPath(Path p) throws IOException {
String abs = p.toAbsolutePath().normalize().toString().replace('\\', '/');
return abs;
}

}

这是只是实现了自定义加载器,主要是构造方法super那里。我们需要优先加载servlet和filter和lisenter,其他的访问到时再加载。

1
2
final Path classPath;
final Path[] libJars;

构造方法添加:

1
2
3
4
5
6
this.classPath = classPath.toAbsolutePath().normalize();
this.libJars = Files.list(libPath).filter(p -> p.toString().endsWith(".jar")).map(p -> p.toAbsolutePath().normalize()).sorted().toArray(Path[]::new);
logger.info("set classes path: {}", this.classPath);
Arrays.stream(this.libJars).forEach(p -> {
logger.info("set jar path: {}", p);
});
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 scanClassPath(Consumer<Resource> handler) {
scanClassPath0(handler, this.classPath, this.classPath);
}

void scanClassPath0(Consumer<Resource> handler, Path basePath, Path path) {
try {
Files.list(path).sorted().forEach(p -> {
if (Files.isDirectory(p)) {
scanClassPath0(handler, basePath, p);
} else if (Files.isRegularFile(p)) {
Path subPath = basePath.relativize(p);
handler.accept(new Resource(p, subPath.toString().replace('\\', '/')));
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public void scanJar(Consumer<Resource> handler) {
try {
for (Path jarPath : this.libJars) {
scanJar0(handler, jarPath);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

void scanJar0(Consumer<Resource> handler, Path jarPath) throws IOException {
JarFile jarFile = new JarFile(jarPath.toFile());
jarFile.stream().filter(entry -> !entry.isDirectory()).forEach(entry -> {
String name = entry.getName();
handler.accept(new Resource(jarPath, name));
});
}

提供了扫描classpath 和jar 中的类方法,转入Consumer进行调用。

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
// set classloader:
var classLoader = new WebAppClassLoader(ps[0], ps[1]);
// scan class:
Set<Class<?>> classSet = new HashSet<>();
Consumer<Resource> handler = (r) -> {
if (r.name().endsWith(".class")) {
String className = r.name().substring(0, r.name().length() - 6).replace('/', '.');
if (className.endsWith("module-info") || className.endsWith("package-info")) {
return;
}
Class<?> clazz;
try {
clazz = classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
logger.warn("load class '{}' failed: {}: {}", className, e.getClass().getSimpleName(), e.getMessage());
return;
} catch (NoClassDefFoundError err) {
logger.error("load class '{}' failed: {}: {}", className, err.getClass().getSimpleName(), err.getMessage());
return;
}
if (clazz.isAnnotationPresent(WebServlet.class)) {
logger.info("Found @WebServlet: {}", clazz.getName());
classSet.add(clazz);
}
if (clazz.isAnnotationPresent(WebFilter.class)) {
logger.info("Found @WebFilter: {}", clazz.getName());
classSet.add(clazz);
}
if (clazz.isAnnotationPresent(WebListener.class)) {
logger.info("Found @WebListener: {}", clazz.getName());
classSet.add(clazz);
}
}
};
classLoader.scanClassPath(handler);
classLoader.scanJar(handler);
List<Class<?>> autoScannedClasses = new ArrayList<>(classSet);

得到了sevlet和filter、lisenter后,servletcontext进行相关修改

1
2
final ClassLoader classLoader;
final Path webRoot;

构造方法转入classloader。

1
this.webRoot = Paths.get(webRoot).normalize().toAbsolutePath();
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
@Override
public ClassLoader getClassLoader() {
return this.classLoader;
}
@Override
public Set<String> getResourcePaths(String path) {
String originPath = path;
if (path.startsWith("/")) {
path = path.substring(1);
}
Path loc = this.webRoot.resolve(path).normalize();
if (loc.startsWith(this.webRoot)) {
if (Files.isDirectory(loc)) {
try {
return Files.list(loc).map(p -> p.getFileName().toString()).collect(Collectors.toSet());
} catch (IOException e) {
logger.warn("list files failed for path: {}", originPath);
}
}
}
return null;
}

@Override
public URL getResource(String path) throws MalformedURLException {
String originPath = path;
if (path.startsWith("/")) {
path = path.substring(1);
}
Path loc = this.webRoot.resolve(path).normalize();
if (loc.startsWith(this.webRoot)) {
return URI.create("file://" + loc.toString()).toURL();
}
throw new MalformedURLException("Path not found: " + originPath);
}

@Override
public InputStream getResourceAsStream(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
Path loc = this.webRoot.resolve(path).normalize();
if (loc.startsWith(this.webRoot)) {
if (Files.isReadable(loc)) {
try {
return new BufferedInputStream(new FileInputStream(loc.toFile()));
} catch (FileNotFoundException e) {
throw new UncheckedIOException(e);
}
}
}
return null;
}

@Override
public String getRealPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
Path loc = this.webRoot.resolve(path).normalize();
if (loc.startsWith(this.webRoot)) {
return loc.toString();
}
return null;
}
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
public void initialize(List<Class<?>> autoScannedClasses) {
if (this.initialized) {
throw new IllegalStateException("Cannot re-initialize.");
}

// register @WebListener:
for (Class<?> c : autoScannedClasses) {
if (c.isAnnotationPresent(WebListener.class)) {
logger.info("auto register @WebListener: {}", c.getName());
@SuppressWarnings("unchecked")
Class<? extends EventListener> clazz = (Class<? extends EventListener>) c;
this.addListener(clazz);
}
}

this.invokeServletContextInitialized();

// register @WebServlet and @WebFilter:
for (Class<?> c : autoScannedClasses) {
WebServlet ws = c.getAnnotation(WebServlet.class);
if (ws != null) {
logger.info("auto register @WebServlet: {}", c.getName());
@SuppressWarnings("unchecked")
Class<? extends Servlet> clazz = (Class<? extends Servlet>) c;
ServletRegistration.Dynamic registration = this.addServlet(AnnoUtils.getServletName(clazz), clazz);
registration.addMapping(AnnoUtils.getServletUrlPatterns(clazz));
registration.setInitParameters(AnnoUtils.getServletInitParams(clazz));
}
WebFilter wf = c.getAnnotation(WebFilter.class);
if (wf != null) {
logger.info("auto register @WebFilter: {}", c.getName());
@SuppressWarnings("unchecked")
Class<? extends Filter> clazz = (Class<? extends Filter>) c;
FilterRegistration.Dynamic registration = this.addFilter(AnnoUtils.getFilterName(clazz), clazz);
registration.addMappingForUrlPatterns(AnnoUtils.getFilterDispatcherTypes(clazz), true, AnnoUtils.getFilterUrlPatterns(clazz));
registration.setInitParameters(AnnoUtils.getFilterInitParams(clazz));
}
}

// init servlets while find default servlet:
Servlet defaultServlet = null;
for (String name : this.servletRegistrations.keySet()) {
var registration = this.servletRegistrations.get(name);
try {
registration.servlet.init(registration.getServletConfig());
for (String urlPattern : registration.getMappings()) {
this.servletMappings.add(new ServletMapping(urlPattern, registration.servlet));
if (urlPattern.equals("/")) {
if (defaultServlet == null) {
defaultServlet = registration.servlet;
logger.info("set default servlet: " + registration.getClassName());
} else {
logger.warn("found duplicate default servlet: " + registration.getClassName());
}
}
}
registration.initialized = true;
} catch (ServletException e) {
logger.error("init servlet failed: " + name + " / " + registration.servlet.getClass().getName(), e);
}
}

// init filters:
for (String name : this.filterRegistrations.keySet()) {
var registration = this.filterRegistrations.get(name);
try {
registration.filter.init(registration.getFilterConfig());
for (String urlPattern : registration.getUrlPatternMappings()) {
this.filterMappings.add(new FilterMapping(name, urlPattern, registration.filter));
}
registration.initialized = true;
} catch (ServletException e) {
logger.error("init filter failed: " + name + " / " + registration.filter.getClass().getName(), e);
}
}
// important: sort by servlet mapping:
Collections.sort(this.servletMappings);
// important: sort by filter name:
Collections.sort(this.filterMappings, Comparator.comparing((FilterMapping f) -> f.filterName).thenComparing(f -> f));

this.initialized = true;
}

有细微的调整对其他类,比如FilterMapping中添加名称用于排序。

对httpconnector的修改:

1
final ClassLoader classLoader;
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 HttpConnector(String host, int port, Executor executor, ClassLoader classLoader, List<Class<?>> autoScannedClasses) throws IOException, ServletException {
this.host = host;
this.port = port;
this.httpServer = HttpServer.create(new InetSocketAddress(this.host, this.port), 0);
this.httpServer.createContext("/", this);
this.httpServer.setExecutor(executor);
this.httpServer.start();

this.classLoader = classLoader;

// init servlet context:
Thread.currentThread().setContextClassLoader(this.classLoader);
ServletContextImpl ctx = new ServletContextImpl(classLoader);
ctx.initialize(autoScannedClasses);
this.servletContext = ctx;
Thread.currentThread().setContextClassLoader(null);
}

@Override
public void handle(HttpExchange exchange) throws IOException {
HttpServletResponseImpl response = new HttpServletResponseImpl(exchange);
HttpServletRequestImpl request = new HttpServletRequestImpl(exchange, servletContext, response);
// process:
try {
Thread.currentThread().setContextClassLoader(this.classLoader);
this.servletContext.process(request, response);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
Thread.currentThread().setContextClassLoader(null);
response.cleanup();
}
}

1
2
3
4
其中:
Thread.currentThread().setContextClassLoader(this.classLoader);
Thread.currentThread().setContextClassLoader(null);
属于模板代码,代码之间使用this.classLoader ,出了之后还是使用的以前的classloader。

布署servlet项目

1
java -jar mytomcat.jar -w hello-webapp-1.0.war

布署spring项目

因为DispatcherServlet 没有加webservlet注解所以无法加载进mytomcat。

所以需要项目中加入

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

/**
* web.xml等效配置:
*
* <code>
* <?xml version="1.0"?>
* <web-app>
* <servlet>
* <servlet-name>dispatcher</servlet-name>
* <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
* <init-param>
* <param-name>contextClass</param-name>
* <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
* </init-param>
* <init-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>com.itranswarp.sample.AppConfig</param-value>
* </init-param>
* </servlet>
* <servlet-mapping>
* <servlet-name>dispatcher</servlet-name>
* <url-pattern>/</url-pattern>
* </servlet-mapping>
* </web-app>
* </code>
*/
@WebServlet(urlPatterns = "/", // default servlet
initParams = { //
@WebInitParam(name = "contextClass", value = "org.springframework.web.context.support.AnnotationConfigWebApplicationContext"),
@WebInitParam(name = "contextConfigLocation", value = "com.itranswarp.sample.AppConfig") })
public class AppDispatcherServlet extends DispatcherServlet {

}

打包后一样启动:

1
java -jar mytomcat.jar -w spring-webapp-1.0.war

报错一般是没有完善相应的servlet方法。

当访问首页时,不能处理/static/xxx.css等静态文件,因为找不到对应的servlet,其实spring 的dispatcherServlet可以处理。

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
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ClassPathUtils {

static final Logger logger = LoggerFactory.getLogger(ClassPathUtils.class);

public static byte[] readBytes(String path) {
try (InputStream input = ClassPathUtils.class.getResourceAsStream(path)) {
if (input == null) {
throw new FileNotFoundException("File not found in classpath: " + path);
}
return input.readAllBytes();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

public static String readString(String path) {
return new String(readBytes(path), StandardCharsets.UTF_8);
}
}
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import utils.ClassPathUtils;
import utils.DateUtils;
import utils.HtmlUtils;

/**
* Default servlet is mapping on "/" and serves as file browsing.
*/
public class DefaultServlet extends HttpServlet {

final Logger logger = LoggerFactory.getLogger(getClass());
String indexTemplate;

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.indexTemplate = ClassPathUtils.readString("/index.html");
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();
logger.info("list file or directory: {}", uri);
if (!uri.startsWith("/")) {
// insecure uri:
logger.debug("skip process insecure uri: {}", uri);
resp.sendError(404, "Not Found");
return;
}
if (uri.equals("/WEB-INF") || uri.startsWith("/WEB-INF/")) {
// prevent access WEB-INF:
logger.debug("prevent access uri: {}", uri);
resp.sendError(403, "Forbidden");
return;
}
if (uri.indexOf("/../") > 0) {
// prevent access /abc/../../xyz:
logger.debug("prevent access insecure uri: {}", uri);
resp.sendError(404, "Not Found");
return;
}
String realPath = req.getServletContext().getRealPath(uri);
Path path = Paths.get(realPath);
logger.debug("try access path: {}", path);
if (uri.endsWith("/")) {
if (Files.isDirectory(path)) {
// list dir:
List<Path> files = Files.list(path).collect(Collectors.toList());
Collections.sort(files, (f1, f2) -> {
var s1 = f1.toString();
var s2 = f2.toString();
return s1.compareToIgnoreCase(s2);
});
StringBuilder sb = new StringBuilder(4096);
if (!uri.equals("/")) {
sb.append(tr(path.getParent(), -1, ".."));
}

for (Path file : files) {
String name = file.getFileName().toString();
long size = -1;
if (Files.isDirectory(file)) {
name = name + "/";
} else if (Files.isRegularFile(file)) {
size = Files.size(file);
}
sb.append(tr(file, size, name));
}
String trs = sb.toString();
String html = this.indexTemplate.replace("${URI}", HtmlUtils.encodeHtml(uri)) //
.replace("${SERVER}", getServletContext().getServerInfo()) //
.replace("${TRS}", trs);
PrintWriter pw = resp.getWriter();
pw.write(html);
pw.flush();
return;
}
} else if (Files.isReadable(path) && Files.isReadable(path)) {
logger.debug("read file: {}", path);
resp.setContentType(getServletContext().getMimeType(uri));
ServletOutputStream output = resp.getOutputStream();
try (InputStream input = new BufferedInputStream(new FileInputStream(path.toFile()))) {
input.transferTo(output);
}
output.flush();
return;
}
resp.sendError(404, "Not Found");
return;
}

static String tr(Path file, long size, String name) throws IOException {
return "<tr><td><a href=\"" + name + "\">" + HtmlUtils.encodeHtml(name) + "</a></td><td>" + size(size) + "</td><td>"
+ DateUtils.formatDateTimeGMT(Files.getLastModifiedTime(file).toMillis()) + "</td>";
}

static String size(long size) {
if (size >= 0) {
if (size > 1024 * 1024 * 1024) {
return String.format("%.3f GB", size / (1024 * 1024 * 1024.0));
}
if (size > 1024 * 1024) {
return String.format("%.3f MB", size / (1024 * 1024.0));
}
if (size > 1024) {
return String.format("%.3f KB", size / 1024.0);
}
return size + " B";
}
return "";
}

}

servletContext:

1
Servlet defaultServlet;

initialize方法

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
// init servlets while find default servlet:
Servlet defaultServlet = null;
for (String name : this.servletRegistrations.keySet()) {
var registration = this.servletRegistrations.get(name);
try {
registration.servlet.init(registration.getServletConfig());
for (String urlPattern : registration.getMappings()) {
this.servletMappings.add(new ServletMapping(urlPattern, registration.servlet));
if (urlPattern.equals("/")) {
if (defaultServlet == null) {
defaultServlet = registration.servlet;
logger.info("set default servlet: " + registration.getClassName());
} else {
logger.warn("found duplicate default servlet: " + registration.getClassName());
}
}
}
registration.initialized = true;
} catch (ServletException e) {
logger.error("init servlet failed: " + name + " / " + registration.servlet.getClass().getName(), e);
}
}
if (defaultServlet == null) {
logger.info("no default servlet. auto register {}...", DefaultServlet.class.getName());
defaultServlet = new DefaultServlet();
try {
defaultServlet.init(new ServletConfig() {
@Override
public String getServletName() {
return "DefaultServlet";
}

@Override
public ServletContext getServletContext() {
return ServletContextImpl.this;
}

@Override
public String getInitParameter(String name) {
return null;
}

@Override
public Enumeration<String> getInitParameterNames() {
return Collections.emptyEnumeration();
}
});
this.servletMappings.add(new ServletMapping("/", defaultServlet));
} catch (ServletException e) {
logger.error("init default servlet failed.", e);
}
}
this.defaultServlet = defaultServlet;

process方法

1
2
3
4
5
6
7
8
9
10
// search servlet:
Servlet servlet = this.defaultServlet;
if (!"/".equals(path)) {
for (ServletMapping mapping : this.servletMappings) {
if (mapping.matches(path)) {
servlet = mapping.servlet;
break;
}
}
}

提供配置参数

思路是 项目提供默认配置文件,加载自定义配置文件,都加载成一个配置类的对象,合并对象参数,把配置对象构造方法传给需要的对象。

总结

对http有了一定的了解

对servlet每个类及方法了解

责任链模式

观察者模式


手写tomcat
http://hanqichuan.com/2024/08/22/webserver/手写tomcat/
作者
韩启川
发布于
2024年8月22日
许可协议