粗汉手撕Tomcat核心原理-Netty版
文章目录
源码地址: https://gitee.com/kylin1991_admin/mini-tomcat/tree/master/nio-tomcat
环境准备
1、maven
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.48.Final</version>
</dependency>
2、插件:Lombok
相关类图展示
1、配置文件web.properties
servlet.one.url=/firstServlet.do
servlet.one.className=org.example.servlet.FirstServlet
2、顶级抽象类NServlet
public abstract class NServlet {
public void service(NRequest request, NResponse response) throws IOException {
if ("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request, response);
} else {
doPost(request, response);
}
}
protected abstract void doGet(NRequest request, NResponse response) throws IOException;
protected abstract void doPost(NRequest request, NResponse response) throws IOException;
}
3、包装NRequst
@Data
public class NRequest {
private String url;
private String method;
private Map<String, Object> parametersMap = new HashMap<>();
private ChannelHandlerContext ctx;
private HttpRequest req;
public NRequest(ChannelHandlerContext ctx, HttpRequest req) {
this.ctx = ctx;
this.req = req;
init();
}
private void init() {
this.method = req.method().toString();
String[] requestUrl = req.uri().split("\\?");
this.setUrl(requestUrl[0]);
if (requestUrl.length > 1) {
String[] paramKeyValue = requestUrl[1].split("&");
for (String keyValue : paramKeyValue) {
String[] params = keyValue.split("=");
this.getParametersMap().put(params[0], params[1]);
}
}
}
}
4、包装NResponse
public class NResponse {
private ChannelHandlerContext ctx;
private HttpRequest req;
public NResponse(ChannelHandlerContext ctx, HttpRequest req) {
this.ctx = ctx;
this.req = req;
}
public void write(String content) throws IOException {
if (content == null || content.length() == 0) {
return;
}
FullHttpResponse response = new DefaultFullHttpResponse(
// 设置http版本为1.1
HttpVersion.HTTP_1_1,
// 设置响应状态码
HttpResponseStatus.OK,
// 设置输出值写出 编码为UTF-8
Unpooled.wrappedBuffer(content.getBytes("UTF-8")));
// 设置响应头
response.headers().set("Content-Type", "text/html");
ctx.writeAndFlush(response);
ctx.close();
}
}
5、NTomcat启动类
public class NTomcat {
// 打开 Tomcat 源码,全局搜索ServerSocket
private static final int PORT = 8080;
private static Map<String, NServlet> serviceMap = new HashMap<>();
private static String WEB_URL = "web.properties";
private void init() {
try {
FileInputStream fileInputStream = new FileInputStream(this.getClass().getResource("/").getPath() + WEB_URL);
Properties properties = new Properties();
properties.load(fileInputStream);
for (Object k : properties.keySet()) {
String key = k.toString();
if (key.endsWith(".url")) {
String keyPrefix = key.replaceAll("\\.url$", "");
String url = properties.getProperty(key);
String className = properties.getProperty(keyPrefix + ".className");
NServlet servlet = (NServlet) Class.forName(className).newInstance();
serviceMap.put(url, servlet);
}
}
} catch (IOException | IllegalAccessException | InstantiationException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public void start() {
init();
// Netty 封装了NIO,Reactor模型,Boss,worker线程
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
// netty 服务 ==>> nio中是ServerSocketChannel
ServerBootstrap server = new ServerBootstrap();
// 链路式编程
server.group(bossGroup, workerGroup)
// 主线程处理类,以后看到这种写法,底层就是用的反射
.channel(NioServerSocketChannel.class)
// 主线程的配置 分配线程最大数量 128
.option(ChannelOption.SO_BACKLOG, 128)
// 子线程处理类,handler
.childHandler(new ChannelInitializer<SocketChannel>() {
// 客户端初始化处理
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 无锁化串行编程,
// Netty 对 HTTP 协议的封装,顺序有要求的
// HttpResponseEncoder 编码器
ch.pipeline().addLast(new HttpResponseEncoder());
// HttpRequestDecoder 解码器
ch.pipeline().addLast(new HttpRequestDecoder());
// 业务处理逻辑
ch.pipeline().addLast(new NTomcatHandler());
}
})
// 针对子线程的配置 保持长连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 启动服务器
try {
ChannelFuture f = server.bind(this.PORT).sync();
System.out.println("N Tomcat 已启动,监听端口是:" + this.PORT);
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static class NTomcatHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
// 转交给我们自己的request 实现
NRequest request = new NRequest(ctx, req);
NResponse response = new NResponse(ctx, req);
// 实际的业务处理
if (serviceMap.containsKey(request.getUrl())) {
serviceMap.get(request.getUrl()).service(request, response);
} else {
response.write("404 - Not Found");
}
}
}
}
public static void main(String[] args) {
new NTomcat().start();
}
}
6、测试服务类
public class FirstServlet extends NServlet {
@Override
protected void doGet(NRequest request, NResponse response) throws IOException {
doPost(request, response);
}
@Override
protected void doPost(NRequest request, NResponse response) throws IOException {
response.write("test first servlet success");
}
}
好了。至此。直接启动BTomcat 就可以访问了。