package main.tomcat;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
interface Servlet {
void service(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
class LoginServlet implements Servlet {
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws Exception {
String userName = request.getParameterValue("userName");
String password = request.getParameterValue("password");
String uri = request.getParameterValue("uri");
String allParameterValue = request.getAllParameterValue();
System.out.println(LocalDateTime.now() + " 【" + Thread.currentThread().getName() + "】线程===>正在处理客户端请求,需耗时3s...");
System.out.println("开始倒计时:");
TimeUnit.SECONDS.sleep(1);
System.out.println("3");
TimeUnit.SECONDS.sleep(1);
System.out.println("2");
TimeUnit.SECONDS.sleep(1);
System.out.println("1");
TimeUnit.SECONDS.sleep(1);
SocketChannel printWriter = response.getPrintWriter();
StringBuffer result = ResponsePage.build(request.getParameterValue("uri"));
result.append("接受到你的请求参数是:" + allParameterValue + "");
// 处理完成,开始会写给客户端数据
System.out.println(LocalDateTime.now() + " 【" + Thread.currentThread().getName() + "】线程===>处理成功");
printWriter.write(ByteBuffer.wrap(result.toString().getBytes(StandardCharsets.UTF_8)));
System.out.println("nnnn");
}
}
class ResponsePage {
public static StringBuffer build(String pageIndex) {
StringBuffer buffer = new StringBuffer();
buffer.append("HTTP/1.1 200 okn");
buffer.append("Content-Type: text/html; Charset=utf-8nn");
try {
FileInputStream fileInputStream = null;
if (pageIndex.contains("login")) {
fileInputStream = new FileInputStream("/Users/gongweiming/IdeaProjects/NettyProDemo/src/main/tomcat/login.html");
} else {
fileInputStream = new FileInputStream("/Users/gongweiming/IdeaProjects/NettyProDemo/src/main/tomcat/404.html");
}
FileChannel channel = fileInputStream.getChannel();
ByteBuffer byteBuf = ByteBuffer.allocate(1024);
int len = 0;
while ((len = channel.read(byteBuf)) != -1) {
}
String content = new String(byteBuf.array(), 0, 1024 - byteBuf.remaining());
// System.out.println(">>>>>>>>>"+content);
buffer.append(content);
} catch (Exception e) {
e.printStackTrace();
}
return buffer;
}
}
public class TomcatBootStrap {
private static final ConcurrentHashMap servletMap = new ConcurrentHashMap<>();
public static void main(String[] args) throws Exception {
initWebXML();
startTomcat(8080);
}
private static void initWebXML() throws Exception {
FileInputStream fileInputStream = new FileInputStream("/Users/gongweiming/IdeaProjects/NettyProDemo/src/main/tomcat/web.xml");
BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream));
String line = "";
String key = null;
String value = null;
while ((line = reader.readLine()) != null) {
if (line.contains("pattern")) {
String[] split = line.split("/");
key = "/" + split[1].substring(0, split[1].length() - 1);
}
if (line.contains("class")) {
value = line.split(">")[1].split("<")[0];
}
}
if (Objects.nonNull(key) && Objects.nonNull(value)) {
servletMap.put(key, new LoginServlet());
}
}
private static void startTomcat(int port) throws Exception {
// 开启服务通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 监听端口
serverSocketChannel.bind(new InetSocketAddress(port));
// 设置成异步非阻塞模式,如果没有事件发生就返回 null
serverSocketChannel.configureBlocking(false);
// 开启线程处理业务
doExecute(serverSocketChannel);
// 关闭资源
// 如果将 serverSocketChannel.accept() 方法放到另一个线程中执行的话这里不要关闭,否则拿不到 SocketChannel,
// 如果使用 Completable 接口调用 serverSocketChannel.accept() 方法,注意它使用的是 ForkJoinPool 是个守护线程
// 所以启动主线程,如果使用这个 ForkJoinPool 框架开启新线程,可以在主线程上面阻塞住,不要关闭了,因为 ForkJoinPool 是守护线程
serverSocketChannel.close();
}
private static final int BUFFER_SIZE = 1024;
private static void doExecute(ServerSocketChannel serverSocketChannel) throws Exception {
System.out.println("Tomcat is started,port is 8080...");
while (true) {
// 如果有 accept 事件发生,就不会返回 null
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) {
CompletableFuture.runAsync(() -> {
try {
HttpServletResponse response = new HttpServletResponse();
response.setPrintWriter(socketChannel);
HttpServletRequest request = requestResolver(socketChannel);
if (Objects.isNull(request)) {
return;
}
Servlet handler = dispatcherServlet(request);
if (Objects.isNull(handler)) {
// 跳转到 404 页面,终结此次请求
response.getPrintWriter().write(ByteBuffer.wrap(ResponsePage.build("404").toString().getBytes(StandardCharsets.UTF_8)));
socketChannel.close();
return;
}
handler.service(request, response);
response.getPrintWriter().close();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
private static Servlet dispatcherServlet(HttpServletRequest request) throws InterruptedException {
return servletMap.get(request.getParameterValue("uri"));
}
private static HttpServletRequest requestResolver(SocketChannel socketChannel) throws Exception {
HttpServletRequest request = new HttpServletRequest();
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
socketChannel.read(buffer);
byte[] bytes = buffer.array();
String content = new String(bytes, 0, BUFFER_SIZE - buffer.remaining());
System.out.println(LocalDateTime.now() + "【" + Thread.currentThread().getName() + "】线程===>接收到客户端请求,解析后,数据如下所示:n");
if (content.contains("favicon")) {
return null;
}
System.out.println("=============================================================================================================");
System.out.println(content);
System.out.println("=================================================++++++【开始处理客户单请求】++++================================");
if (!content.contains("favicon")) {
String firstLine = content.split("n")[0];
if (firstLine.contains("HTTP")) {
request.setParameterValue("uri", "/" + (firstLine.contains("?") ? firstLine.split("/")[1].split("\?")[0] : firstLine.split("/")[1].split(" ")[0]));
;
if (firstLine.contains("?") && firstLine.contains("=")) {
String s1 = firstLine.split("\?")[1].split(" ")[0];
String[] split = s1.split("&");
Arrays.stream(split).forEach(e -> {
String[] split1 = e.split("=");
request.setParameterValue(split1[0], split1[1]);
});
}
}
}
return request;
}
}
class HttpServletResponse {
private SocketChannel printWriter;
public static HttpServletResponse SUCCESS() {
HttpServletResponse response = new HttpServletResponse();
return response;
}
public SocketChannel getPrintWriter() {
return printWriter;
}
public void setPrintWriter(SocketChannel printWriter) {
this.printWriter = printWriter;
}
}
class HttpServletRequest {
private Map parameterValue = new ConcurrentHashMap<>();
public String getAllParameterValue() {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("
");
parameterValue.forEach((k, v) -> {
stringBuffer.append(k + "=" + v + "
");
});
return stringBuffer.toString();
}
public String getParameterValue(String key) {
return parameterValue.get(key);
}
public void setParameterValue(String key, String value) {
parameterValue.put(key, value);
}
}
然后开启多个浏览器窗口,地址栏输入
http://localhost:8080/login?userName=10&password=1 http://localhost:8080/login http://localhost:8080/abcxxx?userName=10&password=1 http://localhost:8080/abcxxx
控制台输出日志,如下:
Tomcat is started,port is 8080... 2022-08-09T17:29:39.679【ForkJoinPool.commonPool-worker-9】线程===>接收到客户端请求,解析后,数据如下所示: ============================================================================================================= GET /login HTTP/1.1 Host: localhost:8080 Connection: keep-alive Cache-Control: max-age=0 sec-ch-ua: ".Not/A)Brand";v="99", "Google Chrome";v="103", "Chromium";v="103" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "macOS" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: XXL_JOB_LOGIN_IDENTITY=7b226964223a312c22757365726e616d65223a2261646d696e222c2270617373776f7264223a226531306164633339343962613539616262653536653035376632306638383365222c22726f6c65223a312c227065726d697373696f6e223a6e756c6c7d; Idea-159af402=a56b3ddf-5c52-4ba7-a513-b6bc69bef0f1 =================================================++++++【开始处理客户单请求】++++================================ 2022-08-09T17:29:39.681 【ForkJoinPool.commonPool-worker-9】线程===>正在处理客户端请求,需耗时3s... 开始倒计时: 3 2 1 2022-08-09T17:29:43.701 【ForkJoinPool.commonPool-worker-9】线程===>处理成功
页面展示,如下:
总结Tomcat 服务就是一个 Server,具备两点核心功能: Start 启动服务器,Stop 停止服务器;底层通过监听 Socket、接受请求、处理请求、响应请求等主要功能。



