socketWrapper) {
242 | super.reset(socketWrapper);
243 | }
244 |
245 | @Override
246 | protected void doRun() {
247 | //TODO
248 | }
249 | }
250 | //-----------------------------------------------------SocketProcessor end
251 |
252 |
253 | }
254 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lws/lwebserver/core/request/Request.java:
--------------------------------------------------------------------------------
1 | package com.lws.lwebserver.core.request;
2 |
3 | import com.lws.lwebserver.core.constant.CharConstant;
4 | import com.lws.lwebserver.core.constant.CharsetProperties;
5 | import com.lws.lwebserver.core.context.ServletContext;
6 | import com.lws.lwebserver.core.context.WebApplication;
7 | import com.lws.lwebserver.core.cookie.Cookie;
8 | import com.lws.lwebserver.core.enumeration.RequestMethod;
9 | import com.lws.lwebserver.core.exception.RequestInvalidException;
10 | import com.lws.lwebserver.core.exception.RequestParseException;
11 |
12 | import com.lws.lwebserver.core.net.handler.AbstractRequestHandler;
13 | import com.lws.lwebserver.core.request.dispatcher.RequestDispatcher;
14 | import com.lws.lwebserver.core.request.dispatcher.impl.ApplicationRequestDispatcher;
15 |
16 | import com.lws.lwebserver.core.session.HttpSession;
17 | import lombok.Data;
18 | import lombok.extern.slf4j.Slf4j;
19 |
20 | import java.io.BufferedInputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.io.UnsupportedEncodingException;
24 | import java.net.URLDecoder;
25 | import java.util.Arrays;
26 | import java.util.HashMap;
27 | import java.util.List;
28 | import java.util.Map;
29 |
30 | /**
31 | * Created by zl on 2019/03/01.
32 | *
33 | * GET /search?hl=zh-CN&source=hp&q=domety&aq=f&oq= HTTP/1.1
34 | * Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint,
35 | * application/msword, application/x-silverlight
36 | * Referer: http://www.google.cn/
37 | * Accept-Language: zh-cn
38 | * Accept-Encoding: gzip, deflate
39 | * User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)
40 | * Host: www.google.cn
41 | * Connection: Keep-Alive
42 | * Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g;
43 | * NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y-
44 | * FxlRugatx63JLv7CWMD6UB_O_r
45 | *
46 | * POST /search HTTP/1.1
47 | * Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint,
48 | * application/msword, application/x-silverlight
49 | * Referer: http://www.google.cn/
50 | * Accept-Language: zh-cn
51 | * Accept-Encoding: gzip,deflate
52 | * User-Agent: Mozilla/4.0(compatible;MSIE6.0;Windows NT5.1;SV1;.NET CLR2.0.50727;TheWorld)
53 | * Host: www.google.cn
54 | * Connection: Keep-Alive
55 | * Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g;
56 | * NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y-
57 | * FxlRugatx63JLv7CWMD6UB_O_r
58 | *
59 | * hl=zh-CN&source=hp&q=domety
60 | */
61 |
62 | @Data
63 | @Slf4j
64 | public class Request {
65 |
66 | private AbstractRequestHandler requestHandler;
67 | private RequestMethod method;
68 | private String url;
69 | private Map> params;
70 | private Map> headers;
71 | private Map attributes;
72 | private ServletContext servletContext;
73 | private Cookie[] cookies;
74 | private HttpSession session;
75 |
76 | /**
77 | * 获取queryString或者body(表单格式)的键值类型的数据
78 | * @param key
79 | * @return
80 | */
81 | public String getParameter(String key) {
82 | List params = this.params.get(key);
83 | if(params == null) {
84 | return null;
85 | }
86 | return params.get(0);
87 | }
88 |
89 |
90 | /**
91 | * 解析HTTP请求
92 | * 读取请求体只能使用字节流,使用字符流读不到
93 | * @param data
94 | * @throws RequestParseException
95 | */
96 | public Request(byte[] data) throws RequestParseException, RequestInvalidException, IOException {
97 | this.attributes = new HashMap<>();
98 | String[] lines = null;
99 | try {
100 | //支持中文,对中文进行URL解码
101 | lines = URLDecoder.decode(new String(data, CharsetProperties.UTF_8_CHARSET), CharsetProperties.UTF_8).split(CharConstant.CRLF);
102 | } catch (UnsupportedEncodingException e) {
103 | e.printStackTrace();
104 | }
105 | log.info("Request读取完毕");
106 | log.info("请求行: {}", Arrays.toString(lines));
107 | if (lines.length <= 1) {
108 | throw new RequestInvalidException();
109 | }
110 | try {
111 | parseHeaders(lines);
112 | if (headers.containsKey("Content-Length") && !headers.get("Content-Length").get(0).equals("0")) {
113 | parseBody(lines[lines.length - 1]);
114 | }
115 | } catch (Throwable e) {
116 | e.printStackTrace();
117 | throw new RequestParseException();
118 | }
119 |
120 | WebApplication.getServletContext().afterRequestCreated(this);
121 | }
122 |
123 | public void setAttribute(String key, Object value) {
124 | attributes.put(key, value);
125 | }
126 |
127 | public Object getAttribute(String key) {
128 | return attributes.get(key);
129 | }
130 |
131 | public RequestDispatcher getRequestDispatcher(String url) {
132 | return new ApplicationRequestDispatcher(url);
133 | }
134 |
135 | /**
136 | * 如果请求报文中携带JSESSIONID这个Cookie,那么取出对应的session
137 | * 否则创建一个Session,并在响应报文中添加一个响应头Set-Cookie: JSESSIONID=D5A5C79F3C8E8653BC8B4F0860BFDBCD
138 | * 所有从请求报文中得到的Cookie,都会在响应报文中返回
139 | * 服务器只会在客户端第一次请求响应的时候,在响应头上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,
140 | * 接下来在同一个会话的第二第三次响应头里,是不会添加Set-Cookie:“JSESSIONID=XXXXXXX”信息的;
141 | * 即,如果在Cookie中读到的JSESSIONID,那么不会创建新的Session,也不会在响应头中加入Set-Cookie:“JSESSIONID=XXXXXXX”
142 | * 如果没有读到,那么会创建新的Session,并在响应头中加入Set-Cookie:“JSESSIONID=XXXXXXX”
143 | * 如果没有调用getSession,那么不会创建新的Session
144 | *
145 | * @param createIfNotExists 如果为true,那么在不存在session时会创建一个新的session;否则会直接返回null
146 | * @return HttpSession
147 | */
148 | public HttpSession getSession(boolean createIfNotExists) {
149 | if (session != null) {
150 | return session;
151 | }
152 | for (Cookie cookie : cookies) {
153 | if (cookie.getKey().equals("JSESSIONID")) {
154 | HttpSession currentSession = servletContext.getSession(cookie.getValue());
155 | if (currentSession != null) {
156 | session = currentSession;
157 | return session;
158 | }
159 | }
160 | }
161 | if (!createIfNotExists) {
162 | return null;
163 | }
164 | session = servletContext.createSession(requestHandler.getResponse());
165 | return session;
166 | }
167 |
168 | public HttpSession getSession() {
169 | return getSession(true);
170 | }
171 |
172 | public String getServletPath() {
173 | return url;
174 | }
175 |
176 | /**
177 | * GET /index?size=1 HTTP/1.1
178 | * Host: www.enjoytoday.cn
179 | * Connection: keep-alive
180 | * Upgrade-Insecure-Requests: 1
181 | * User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36
182 | * Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,
183 | * @param lines
184 | */
185 | private void parseHeaders(String[] lines) {
186 | log.info("解析请求头");
187 | String firstLine = lines[0];
188 | //解析方法
189 | String[] firstLineSlices = firstLine.split(CharConstant.BLANK);
190 | this.method = RequestMethod.valueOf(firstLineSlices[0]);
191 | log.debug("method:{}", this.method);
192 |
193 | //解析URL
194 | String rawURL = firstLineSlices[1];
195 | String[] urlSlices = rawURL.split("\\?");
196 | this.url = urlSlices[0];
197 | log.debug("url:{}", this.url);
198 |
199 | //解析URL参数
200 | if (urlSlices.length > 1) {
201 | parseParams(urlSlices[1]);
202 | }
203 | log.debug("params:{}", this.params);
204 |
205 |
206 | //解析请求头
207 | String header;
208 | this.headers = new HashMap<>();
209 | for (int i = 1; i < lines.length; i++) {
210 | header = lines[i];
211 | if (header.equals("")) {
212 | break;
213 | }
214 | int colonIndex = header.indexOf(':');
215 | String key = header.substring(0, colonIndex);
216 | String[] values = header.substring(colonIndex + 2).split(",");
217 | headers.put(key, Arrays.asList(values));
218 | }
219 | log.debug("headers:{}", this.headers);
220 |
221 | //解析Cookie
222 |
223 | if (headers.containsKey("Cookie")) {
224 | String[] rawCookies = headers.get("Cookie").get(0).split("; ");
225 | this.cookies = new Cookie[rawCookies.length];
226 | for (int i = 0; i < rawCookies.length; i++) {
227 | String[] kv = rawCookies[i].split("=");
228 | this.cookies[i] = new Cookie(kv[0], kv[1]);
229 | }
230 | headers.remove("Cookie");
231 | } else {
232 | this.cookies = new Cookie[0];
233 | }
234 | log.info("Cookies:{}", Arrays.toString(cookies));
235 | }
236 |
237 | private void parseBody(String body) {
238 | log.info("解析请求体");
239 | byte[] bytes = body.getBytes(CharsetProperties.UTF_8_CHARSET);
240 | List lengths = this.headers.get("Content-Length");
241 | if (lengths != null) {
242 | int length = Integer.parseInt(lengths.get(0));
243 | log.info("length:{}", length);
244 | parseParams(new String(bytes, 0, Math.min(length,bytes.length), CharsetProperties.UTF_8_CHARSET).trim());
245 | } else {
246 | parseParams(body.trim());
247 | }
248 | if (this.params == null) {
249 | this.params = new HashMap<>();
250 | }
251 | }
252 |
253 | private void parseParams(String params) {
254 | String[] urlParams = params.split("&");
255 | if (this.params == null) {
256 | this.params = new HashMap<>();
257 | }
258 | for (String param : urlParams) {
259 | String[] kv = param.split("=");
260 | String key = kv[0];
261 | String[] values = kv[1].split(",");
262 |
263 | this.params.put(key, Arrays.asList(values));
264 | }
265 | }
266 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/lws/lwebserver/core/net/endpoint/nio/Poller.java:
--------------------------------------------------------------------------------
1 | package com.lws.lwebserver.core.net.endpoint.nio;
2 |
3 | import com.lws.lwebserver.core.net.wrapper.SocketWrapperBase;
4 | import com.lws.lwebserver.core.net.wrapper.nio.NioSocketWrapper;
5 | import com.lws.lwebserver.core.util.SynchronizedQueue;
6 | import lombok.extern.slf4j.Slf4j;
7 |
8 | import java.io.IOException;
9 | import java.nio.channels.*;
10 | import java.util.HashMap;
11 | import java.util.Iterator;
12 | import java.util.Map;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.concurrent.atomic.AtomicLong;
15 |
16 | /**
17 | * @author zl
18 | * Poller class 轮询线程
19 | * 主要维护多路复用器和PollerEvent队列
20 | */
21 | @Slf4j
22 | public class Poller implements Runnable {
23 |
24 | private String pollerName;
25 | private Selector selector;
26 | private NioEndpoint endpoint;
27 |
28 | private final SynchronizedQueue events =
29 | new SynchronizedQueue<>();
30 |
31 | private volatile boolean close = false;
32 |
33 | private Map sockets;//所有活跃socket
34 | /**
35 | * 用来标识selector的状态,这里的设计方式极其巧妙!
36 | * -1-------阻塞状态(被系统调用select()阻塞)
37 | * 0--------初始状态(select()返回后,回到初始状态)
38 | * >0--------events存在没有被注册的socket,
39 | */
40 | private AtomicLong wakeupCounter = new AtomicLong(0);
41 |
42 | private volatile int keyCount = 0;
43 |
44 | private long selectorTimeout = 1000;
45 |
46 | public Selector getSelector() {
47 | return selector;
48 | }
49 |
50 | public Poller(NioEndpoint nioEndpoint, String pollerName) throws IOException {
51 | this.selector = Selector.open();//开启多路复用器
52 | this.endpoint = nioEndpoint;
53 | this.pollerName = pollerName;
54 | sockets = new ConcurrentHashMap<>();
55 | }
56 |
57 | /**
58 | * 结束Poller
59 | */
60 | protected void destroy() throws IOException {
61 | for (NioSocketWrapper wrapper : sockets.values()) {
62 | wrapper.close();
63 | }
64 | events.clear();
65 | close = true;//停止轮询
66 | selector.wakeup();
67 | }
68 |
69 | public String getPollerName() {
70 | return pollerName;
71 | }
72 |
73 | /**
74 | * 向selector中添加socket,后台线程检查poller中的触发事件,
75 | * 并将socket交给合适的进程进行处理。
76 | */
77 | @Override
78 | public void run() {
79 | //Loop until destroy()
80 | while (true) {
81 | boolean hasEvents = false;
82 | try {
83 | if (!close) {// 未关闭
84 | events();
85 | if (wakeupCounter.getAndSet(-1) > 0) {
86 | //代表此时events存在未注册的值,立即返回(执行一个non blocking select)
87 | keyCount = selector.selectNow();
88 | } else {
89 | keyCount = selector.select(selectorTimeout);//设置阻塞超时时间
90 | }
91 | wakeupCounter.set(0);
92 | }
93 | if (close) {//关闭
94 | try {
95 | selector.close();
96 | } catch (IOException ioe) {
97 | log.error("endpoint.nio.selectorCloseFail", ioe);
98 | }
99 | break;
100 | }
101 | } catch (Throwable e) {
102 | log.error("", e);
103 | continue;
104 | }
105 | Iterator iterator =
106 | keyCount > 0 ? selector.selectedKeys().iterator() : null;
107 | while (iterator != null && iterator.hasNext()) {
108 | SelectionKey sk = iterator.next();
109 | /**
110 | * 获得可操作对象
111 | */
112 | NioSocketWrapper attachment = (NioSocketWrapper) sk.attachment();
113 | if (!sk.isReadable()) {
114 | iterator.remove();
115 | }
116 | if (null == attachment) {
117 | iterator.remove();
118 | } else {
119 | iterator.remove();
120 |
121 | /**粗略概况一下接下来的流程(Tomcat源码的流程,但是接下来我采用的是简化的流程)
122 | *
123 | * |__交由processKey(SelectionKey sk, NioSocketWrapper attachment),判断endponit,attachment的各种状态,判断出对socket的操作SocketEvent,
124 | * 这里还有对数据拷贝优化系统调用sendfile的支持;
125 | * |__processSocket(),创建处理的线程SocketProcess,放入线程池并且触发线程;
126 | * |__SocketProcessor.run,判断是否是SSL/TLS的请求: |___1) No TLS handshaking required. Let the handler process this socket / event combination.
127 | * |___2) Unable to complete the TLS handshake. Treat it as if the handshake failed.
128 | * |___3) The handshake process reads/writes from/to the socket. status may therefore be OPEN_WRITE once the handshake completes.
129 | * However, the handshake happens when the socket is opened so the status must always be OPEN_READ after it completes.
130 | * It is OK to always set this as it is only used if the handshake completes.
131 | * 上面状态1)请求不是TLS握手,则正常由Handler.process处理。
132 | * |__ process,针对不同协议做相应的一系列处理(还包括对keep-alive的处理)(这里头皮发麻_(:з)∠)_)
133 | * wrapper.registerReadInterest();//此方法对socket再次注册到poller中
134 | *
135 | */
136 | processKey(sk, attachment);
137 | }
138 | }
139 | }
140 | }
141 |
142 | /**
143 | * 返回可处理的key
144 | *
145 | * @param sk
146 | * @param attachment
147 | */
148 | protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
149 | try {
150 | if (close) {
151 | cancelledKey(sk);
152 | } else if (sk.isValid() && attachment != null) {
153 | boolean closeSocket = false;
154 | if (!endpoint.processSocket(attachment)) {
155 | closeSocket = true;
156 | }
157 | if (closeSocket) {
158 | cancelledKey(sk);
159 | }
160 | } else {
161 | cancelledKey(sk);
162 | }
163 | } catch (CancelledKeyException c) {
164 | cancelledKey(sk);
165 | } catch (Throwable t) {
166 | log.error("", t);
167 | }
168 | }
169 |
170 | /**
171 | * 注册到PollerEvent
172 | *
173 | * @param socketChannel
174 | * @param isNew
175 | */
176 | public void register(SocketChannel socketChannel, boolean isNew) {
177 | NioSocketWrapper socketWrapper;
178 | if (isNew) {//is new socket
179 | socketWrapper = new NioSocketWrapper(socketChannel, endpoint, this);//包装socketchannel
180 | sockets.put(socketChannel, socketWrapper);//缓存住,用于管理socket
181 | } else {//keep-alive 长连接
182 | socketWrapper = sockets.get(socketChannel);
183 | socketWrapper.setWorking(false);
184 | }
185 | socketWrapper.setWaitBegin(System.currentTimeMillis());
186 | addEvent(new PollerEvent(socketWrapper));//注册到PollerEvent
187 | }
188 |
189 | private void addEvent(PollerEvent event) {
190 | events.offer(event);
191 | /**
192 | *某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。
193 | * 只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。
194 | * 阻塞在select()方法上的线程会立马返回,然后注册evens中的PollerEvent
195 | */
196 | if (wakeupCounter.incrementAndGet() == 0) selector.wakeup();
197 | }
198 |
199 | /**
200 | * 将队列中的注册事件全部执行(注册到selector),并且清空队列
201 | *
202 | * @return
203 | */
204 | private boolean events() {
205 | boolean result = false;
206 | PollerEvent pe = null;
207 | /**
208 | * pop() 从此列表所表示的堆栈处弹出一个元素;
209 | * poll() 获取并移除此列表的头(第一个元素);
210 | * 将队列中的注册事件全部执行,并且清空队列
211 | */
212 | for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++) {
213 | result = true;
214 | pe.run();
215 | pe.reset();
216 | //TODO 这里tomcat里做了个缓存操作 eventCache
217 | }
218 | return result;
219 | }
220 |
221 | public NioSocketWrapper cancelledKey(SelectionKey key) {
222 | NioSocketWrapper ka = null;
223 | try {
224 | if (null == key) {
225 | return null;
226 | }
227 | ka = (NioSocketWrapper) key.attach(null);
228 | /* if(null!=ka){
229 |
230 | }*/
231 | if (key.isValid()) {
232 | key.cancel();
233 | }
234 | if (null != ka) {
235 | try {
236 | ka.getSocket().close();
237 | } catch (Exception e) {
238 | log.info("endpoint.debug.socketCloseFail" + e);
239 | }
240 | }
241 | if (key.channel().isOpen()) {
242 | try {
243 | key.channel().close();
244 | } catch (Exception e) {
245 | log.info("endpoint.debug.channelCloseFail" + e);
246 | }
247 | }
248 | } catch (Throwable e) {
249 | if (log.isDebugEnabled()) log.error("", e);
250 | }
251 | return ka;
252 | }
253 |
254 |
255 | //-----------------------------------------------------PollerEvent start
256 |
257 | /**
258 | * Cacheable object for poller events to avoid GC
259 | * 缓存轮询事件,避免GC
260 | * 并将事件在合适时机注册到Selector中
261 | * 注意这里涉及两种注册:
262 | * 1)accept()到客户机的socketchannel后注册到PollerEvent队列(events);
263 | * 2)然后轮询线程(Poller),在每一次轮询之前,调用events(),将所有PollerEvent,注册到Selector.
264 | */
265 | private static class PollerEvent implements Runnable {
266 |
267 | private NioSocketWrapper socketWrapper;
268 |
269 | public PollerEvent(NioSocketWrapper socketWrapper) {
270 | reset(socketWrapper);
271 | }
272 |
273 | public void reset(NioSocketWrapper w) {
274 | socketWrapper = w;
275 | }
276 |
277 | public void reset() {
278 | reset(null);
279 | }
280 |
281 | @Override
282 | public void run() {
283 | log.info("将SocketChannel的读事件注册到Poller的selector中");
284 | try {
285 | if (socketWrapper.getSocket().isOpen()) {
286 | /**注册并且标记当前服务的通道状态
287 | * register(Selector,int)
288 | * int---状态编码
289 | * OP_READ: 可读标记位
290 | * OP_WRITE: 可写标记位
291 | * OP_CONNECT: 连接建立后的标记
292 | * OP_ACCEPT: 连接成功的标记位
293 | */
294 | socketWrapper.getSocket().register(socketWrapper.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);
295 | } else {
296 | log.error("socket已经被关闭,无法注册到Poller", socketWrapper.getSocket());
297 | }
298 | } catch (ClosedChannelException e) {
299 | e.printStackTrace();
300 | }
301 | }
302 | }
303 | //-----------------------------------------------------PollerEvent end
304 |
305 | /**
306 | * 清理长连接的socket
307 | */
308 | public void cleanTimeoutSockets() {
309 | for (Iterator> it = sockets.entrySet().iterator(); it.hasNext(); ) {
310 | NioSocketWrapper wrapper = it.next().getValue();
311 | log.info("缓存中的socket:{}", wrapper);
312 | if (!wrapper.getSocket().isConnected()) {
313 | log.info("该socket已被关闭");
314 | it.remove();
315 | continue;
316 | }
317 | if (wrapper.isWorking()) {
318 | log.info("该socket正在工作中,不予关闭");
319 | continue;
320 | }
321 | if (System.currentTimeMillis() - wrapper.getWaitBegin() > endpoint.getKeepAliveTimeout()) {
322 | // 反注册
323 | log.info("{} keepAlive已过期", wrapper.getSocket());
324 | try {
325 | wrapper.close();
326 | } catch (IOException e) {
327 | e.printStackTrace();
328 | }
329 | it.remove();
330 | }
331 | }
332 | }
333 |
334 | }
335 |
--------------------------------------------------------------------------------
/core/src/main/java/com/lws/lwebserver/core/context/ServletContext.java:
--------------------------------------------------------------------------------
1 | package com.lws.lwebserver.core.context;
2 |
3 | import com.lws.lwebserver.core.context.holder.FilterHolder;
4 | import com.lws.lwebserver.core.context.holder.ServletHolder;
5 | import com.lws.lwebserver.core.cookie.Cookie;
6 | import com.lws.lwebserver.core.exception.ServletNotFoundException;
7 | import com.lws.lwebserver.core.exception.FilterNotFoundException;
8 | import com.lws.lwebserver.core.fliter.Filter;
9 | import com.lws.lwebserver.core.listener.HttpSessionListener;
10 | import com.lws.lwebserver.core.listener.ServletContextListener;
11 | import com.lws.lwebserver.core.listener.ServletRequestListener;
12 | import com.lws.lwebserver.core.listener.event.HttpSessionEvent;
13 | import com.lws.lwebserver.core.listener.event.ServletContextEvent;
14 | import com.lws.lwebserver.core.listener.event.ServletRequestEvent;
15 | import com.lws.lwebserver.core.request.Request;
16 | import com.lws.lwebserver.core.response.Response;
17 | import com.lws.lwebserver.core.servlet.Servlet;
18 | import com.lws.lwebserver.core.session.HttpSession;
19 | import com.lws.lwebserver.core.session.IdleSessionCleaner;
20 | import com.lws.lwebserver.core.util.UUIDUtil;
21 | import com.lws.lwebserver.core.util.XMLUtil;
22 | import lombok.Data;
23 | import lombok.extern.slf4j.Slf4j;
24 | import org.dom4j.Document;
25 | import org.dom4j.Element;
26 | import org.springframework.util.AntPathMatcher;
27 |
28 | import java.time.Duration;
29 | import java.time.Instant;
30 | import java.util.*;
31 | import java.util.concurrent.ConcurrentHashMap;
32 | import java.util.stream.Collectors;
33 |
34 | import static com.lws.lwebserver.core.constant.Const.DEFAULT_SERVLET_ALIAS;
35 | import static com.lws.lwebserver.core.constant.Const.DEFAULT_SESSION_EXPIRE_TIME;
36 |
37 | /**
38 | * @Author: zl
39 | * @Date: 2019/3/16 11:48
40 | */
41 | @Data
42 | @Slf4j
43 | public class ServletContext {
44 | /**
45 | * 别名->类名
46 | * 一个Servlet类只能有一个Servlet别名,一个Servlet别名只能对应一个Servlet类
47 | */
48 | private Map servlets;
49 | /**
50 | * 一个Servlet可以对应多个URL,一个URL只能对应一个Servlet
51 | * URL Pattern -> Servlet别名
52 | */
53 | private Map servletMapping;
54 |
55 |
56 | /**
57 | * 别名->类名
58 | */
59 | private Map filters;
60 | /**
61 | * URL Pattern -> 别名列表,注意同一个URLPattern可以对应多个Filter,但只能对应一个Servlet
62 | */
63 | private Map> filterMapping;
64 |
65 | /**
66 | * 监听器们
67 | */
68 | private List servletContextListeners;
69 | private List httpSessionListeners;
70 | private List servletRequestListeners;
71 |
72 | /**
73 | * 域
74 | */
75 | private Map attributes;
76 | /**
77 | * 整个应用对应的session们
78 | */
79 | private Map sessions;
80 | /**
81 | * 路径匹配器,由Spring提供
82 | */
83 | private AntPathMatcher matcher;//TODO
84 |
85 | private IdleSessionCleaner idleSessionCleaner;//定时清理Session
86 |
87 |
88 | public ServletContext() throws IllegalAccessException, ClassNotFoundException, InstantiationException {
89 | init();
90 | }
91 |
92 | /**
93 | * 由URL得到对应的一个Servlet实例
94 | *
95 | * @param url
96 | * @return
97 | * @throws ServletNotFoundException
98 | */
99 | public Servlet mapServlet(String url) throws ServletNotFoundException {
100 | // 1、精确匹配
101 | String servletAlias = servletMapping.get(url);
102 | if (servletAlias != null) {
103 | return initAndGetServlet(servletAlias);
104 | }
105 | // 2、路径匹配
106 | List matchingPatterns = new ArrayList<>();
107 | Set patterns = servletMapping.keySet();
108 | for (String pattern : patterns) {
109 | if (matcher.match(pattern, url)) {
110 | matchingPatterns.add(pattern);
111 | }
112 | }
113 |
114 | if (!matchingPatterns.isEmpty()) {
115 | Comparator patternComparator = matcher.getPatternComparator(url);
116 | Collections.sort(matchingPatterns, patternComparator);
117 | String bestMatch = matchingPatterns.get(0);
118 | return initAndGetServlet(bestMatch);
119 | }
120 | return initAndGetServlet(DEFAULT_SERVLET_ALIAS);
121 | }
122 |
123 | /**
124 | * 初始化并获取Servlet实例,如果已经初始化过则直接返回
125 | *
126 | * @param servletAlias
127 | * @return
128 | * @throws ServletNotFoundException
129 | */
130 | private Servlet initAndGetServlet(String servletAlias) throws ServletNotFoundException {
131 | ServletHolder servletHolder = servlets.get(servletAlias);
132 | if (servletHolder == null) {
133 | throw new ServletNotFoundException();
134 | }
135 | if (servletHolder.getServlet() == null) {
136 | try {
137 | Servlet servlet = (Servlet) Class.forName(servletHolder.getServletClass()).newInstance();
138 | servlet.init();
139 | servletHolder.setServlet(servlet);
140 | } catch (InstantiationException e) {
141 | e.printStackTrace();
142 | } catch (IllegalAccessException e) {
143 | e.printStackTrace();
144 | } catch (ClassNotFoundException e) {
145 | e.printStackTrace();
146 | }
147 | }
148 | return servletHolder.getServlet();
149 | }
150 |
151 |
152 | /**
153 | * 由URL得到一系列匹配的Filter实例
154 | *
155 | * @param url
156 | * @return
157 | */
158 | public List mapFilter(String url) throws FilterNotFoundException {
159 | List matchingPatterns = new ArrayList<>();
160 | Set patterns = filterMapping.keySet();
161 | for (String pattern : patterns) {
162 | if (matcher.match(pattern, url)) {
163 | matchingPatterns.add(pattern);
164 | }
165 | }
166 | //flatMap将一对多转化为一对一
167 | Set filterAliases = matchingPatterns.stream().flatMap(pattern -> this.filterMapping.get(pattern).stream()).collect(Collectors.toSet());
168 | List result = new ArrayList<>();
169 | for (String alias : filterAliases) {
170 | result.add(initAndGetFilter(alias));
171 | }
172 | return result;
173 | }
174 |
175 | /**
176 | * 初始化并返回Filter实例,如果已经初始化过则直接返回
177 | *
178 | * @param filterAlias
179 | * @return
180 | * @throws FilterNotFoundException
181 | */
182 | private Filter initAndGetFilter(String filterAlias) throws FilterNotFoundException {
183 | FilterHolder filterHolder = filters.get(filterAlias);
184 | if (filterHolder == null) {
185 | throw new FilterNotFoundException();
186 | }
187 | if (filterHolder.getFilter() == null) {
188 | try {
189 | Filter filter = (Filter) Class.forName(filterHolder.getFilterClass()).newInstance();
190 | filter.init();
191 | filterHolder.setFilter(filter);
192 | } catch (InstantiationException e) {
193 | e.printStackTrace();
194 | } catch (IllegalAccessException e) {
195 | e.printStackTrace();
196 | } catch (ClassNotFoundException e) {
197 | e.printStackTrace();
198 | }
199 | }
200 | return filterHolder.getFilter();
201 | }
202 |
203 | /**
204 | * 应用初始化
205 | *
206 | * @throws IllegalAccessException
207 | * @throws InstantiationException
208 | * @throws ClassNotFoundException
209 | */
210 | public void init() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
211 | this.servlets = new HashMap<>();
212 | this.servletMapping = new HashMap<>();
213 | this.attributes = new ConcurrentHashMap<>();
214 | this.sessions = new ConcurrentHashMap<>();
215 | this.filters = new HashMap<>();
216 | this.filterMapping = new HashMap<>();
217 | this.matcher = new AntPathMatcher();
218 | //TODO 清理session
219 | //this.idleSessionCleaner = new IdleSessionCleaner();
220 | //this.idleSessionCleaner.start();
221 |
222 | this.servletContextListeners = new ArrayList<>();
223 | this.httpSessionListeners = new ArrayList<>();
224 | this.servletRequestListeners = new ArrayList<>();
225 |
226 | parseConfig();//解析web.xml
227 | //Listener
228 | ServletContextEvent servletContextEvent = new ServletContextEvent(this);
229 | for (ServletContextListener listener : servletContextListeners) {
230 | listener.contextInitialized(servletContextEvent);
231 | }
232 | }
233 |
234 | /**
235 | * 应用关闭前被调用
236 | */
237 | public void destroy() {
238 | servlets.values().forEach(servletHolder -> {
239 | if (servletHolder.getServlet() != null) {
240 | servletHolder.getServlet().destroy();
241 | }
242 | });
243 | filters.values().forEach(filterHolder -> {
244 | if (filterHolder.getFilter() != null) {
245 | filterHolder.getFilter().destroy();
246 | }
247 | });
248 | //Listener
249 | ServletContextEvent servletContextEvent = new ServletContextEvent(this);
250 | for (ServletContextListener listener : servletContextListeners) {
251 | listener.contextDestroyed(servletContextEvent);
252 | }
253 | }
254 |
255 | /**
256 | * web.xml文件解析,比如servlet,filter,listener等
257 | *
258 | * @throws ClassNotFoundException
259 | * @throws IllegalAccessException
260 | * @throws InstantiationException
261 | */
262 | private void parseConfig() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
263 | Document doc = XMLUtil.getDocument(ServletContext.class.getResourceAsStream("/web.xml"));
264 | Element root = doc.getRootElement();
265 | // 解析servlet
266 | List servlets = root.elements("servlet");
267 | for (Element servletEle : servlets) {
268 | String key = servletEle.element("servlet-name").getText();
269 | String value = servletEle.element("servlet-class").getText();
270 | this.servlets.put(key, new ServletHolder(value));
271 | }
272 | //解析Servlet-mapping'
273 | List servletMapping = root.elements("servlet-mapping");
274 | for (Element mapping : servletMapping) {
275 | List urlPatterns = mapping.elements("url-pattern");//一个servlet可以对应多个url
276 | String value = mapping.element("servlet-name").getText();
277 | for (Element urlPattern : urlPatterns) {
278 | this.servletMapping.put(urlPattern.getText(), value);
279 | }
280 | }
281 |
282 | // 解析 filter
283 | List filters = root.elements("filter");
284 | for (Element filterEle : filters) {
285 | String key = filterEle.element("filter-name").getText();
286 | String value = filterEle.element("filter-class").getText();
287 | this.filters.put(key, new FilterHolder(value));
288 | }
289 | //解析 filter-mapping
290 | List filterMapping = root.elements("filter-mapping");
291 | for (Element mapping : filterMapping) {
292 | List urlPatterns = mapping.elements("url-pattern");//一个filter可以对应多个url,一个url也可以对应不同的filter
293 | String value = mapping.element("filter-name").getText();
294 | for (Element urlPattern : urlPatterns) {
295 | /**
296 | * 判断该url是否已经存在过,如果存在,即一个url对应多个filter的情况,
297 | * 例如:/**
298 | */
299 | List values = this.filterMapping.get(urlPattern.getText());
300 | if (values == null) {
301 | values = new ArrayList<>();
302 | }
303 | values.add(value);
304 | this.filterMapping.put(urlPattern.getText(), values);
305 | }
306 | }
307 |
308 | // 解析listener
309 | Element listener = root.element("listener");
310 | List listenerEles = listener.elements("listener-class");
311 | for (Element listenerEle : listenerEles) {
312 | EventListener eventListener = (EventListener) Class.forName(listenerEle.getText()).newInstance();
313 | if (eventListener instanceof ServletContextListener) {
314 | servletContextListeners.add((ServletContextListener) eventListener);
315 | }
316 | if (eventListener instanceof HttpSessionListener) {
317 | httpSessionListeners.add((HttpSessionListener) eventListener);
318 | }
319 | if (eventListener instanceof ServletRequestListener) {
320 | servletRequestListeners.add((ServletRequestListener) eventListener);
321 | }
322 | }
323 | }
324 |
325 | /**
326 | * 获取session
327 | * @param JSESSIONID
328 | * @return
329 | */
330 | public HttpSession getSession(String JSESSIONID) {
331 | return sessions.get(JSESSIONID);
332 | }
333 |
334 | /**
335 | * 创建session
336 | * @param response
337 | * @return
338 | */
339 | public HttpSession createSession(Response response) {
340 | HttpSession session = new HttpSession(UUIDUtil.uuid());
341 | sessions.put(session.getId(), session);
342 | response.addCookie(new Cookie("JSESSIONID", session.getId()));
343 |
344 | HttpSessionEvent httpSessionEvent = new HttpSessionEvent(session);
345 | for (HttpSessionListener listener : httpSessionListeners) {
346 | listener.sessionCreated(httpSessionEvent);
347 | }
348 | return session;
349 | }
350 |
351 | /**
352 | * 销毁session
353 | * @param session
354 | */
355 | public void invalidateSession(HttpSession session) {
356 | sessions.remove(session.getId());
357 | afterSessionDestroyed(session);
358 | }
359 |
360 | /**
361 | * 清除空闲的session
362 | * 由于ConcurrentHashMap是线程安全的,所以remove不需要进行加锁
363 | */
364 | public void cleanIdleSessions() {
365 | for (Iterator> it = sessions.entrySet().iterator(); it.hasNext(); ) {
366 | Map.Entry entry = it.next();
367 | if (Duration.between(entry.getValue().getLastAccessed(), Instant.now()).getSeconds() >= DEFAULT_SESSION_EXPIRE_TIME) {
368 | // log.info("该session {} 已过期", entry.getKey());
369 | afterSessionDestroyed(entry.getValue());
370 | it.remove();
371 | }
372 | }
373 | }
374 |
375 | private void afterSessionDestroyed(HttpSession session) {
376 | HttpSessionEvent httpSessionEvent = new HttpSessionEvent(session);
377 | for (HttpSessionListener listener : httpSessionListeners) {
378 | listener.sessionDestroyed(httpSessionEvent);
379 | }
380 | }
381 |
382 | public void afterRequestCreated(Request request) {
383 | ServletRequestEvent servletRequestEvent = new ServletRequestEvent(this, request);
384 | for (ServletRequestListener listener : servletRequestListeners) {
385 | listener.requestInitialized(servletRequestEvent);
386 | }
387 | }
388 |
389 | public void afterRequestDestroyed(Request request) {
390 | ServletRequestEvent servletRequestEvent = new ServletRequestEvent(this, request);
391 | for (ServletRequestListener listener : servletRequestListeners) {
392 | listener.requestDestroyed(servletRequestEvent);
393 | }
394 | }
395 |
396 | public Object getAttribute(String key) {
397 | return attributes.get(key);
398 | }
399 |
400 | public void setAttribute(String key, Object value) {
401 | attributes.put(key, value);
402 | }
403 | }
404 |
--------------------------------------------------------------------------------