基于Netty手写一个简易Tomcat
# 基于Netty手写一个简易的Tomcat容器
本文主要基于传统的BIO来实现一个简单的Http请求处理过程;
1、Servlet请求无非就是doGet/doPost,所以我们定义抽象Servlet记忆GET/POST方法;
2、基于Netty API实现CS通信;
3、模拟Spring加载配置文件,注册请求以及控制器;

## Netty版本
```text
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
```
## GlRequest 基于Netty&HttpRequest的API操作,非常简单
```text
public class GlRequest {
private ChannelHandlerContext ctx;
private HttpRequest req;
public GlRequest(ChannelHandlerContext ctx, HttpRequest req) {
this.ctx = ctx;
this.req = req;
}
public String getUrl() {
return this.req.uri();
}
public String getMethod() {
return this.req.method().name();
}
public Map<String, List<String>> getParams() {
QueryStringDecoder decoder = new QueryStringDecoder(req.uri());
return decoder.parameters();
}
public String getParam(String name) {
Map<String, List<String>> params = getParams();
List<String> strings = params.get(name);
if (strings == null) {
return null;
}
return strings.get(0);
}
}
```
## GlResponse 基于Netty&FullHttpResponse的API操作
> FullHttpResponse作为返回请求的主体;
```text
public class GlResponse {
private ChannelHandlerContext ctx;
private HttpRequest req;
public GlResponse(ChannelHandlerContext ctx, HttpRequest req) {
this.req = req;
this.ctx = ctx;
}
public void write(String string) throws Exception {
if (string == null || string.length() == 0) {
return;
}
try {
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(string.getBytes("UTF-8"))
);
response.headers().set("Content-Type", "text/html");
ctx.write(response);
} catch (Exception e) {
e.printStackTrace();
} finally {
ctx.flush();
ctx.close();
}
}
}
```
## GlServlet 定义抽象servlet,定义GET方法和POST方法
> 定义抽象的Servlet和doGet方法和doPost方法,具体的业务去实现自己的方法和逻辑;
```text
public abstract class GlServlet {
private final static String GET = "GET";
public void service(GlRequest request, GlResponse response) throws Exception {
if (GET.equals(request.getMethod())) {
doGet(request, response);
} else {
doPost(request, response);
}
}
public abstract void doGet(GlRequest request, GlResponse response) throws Exception;
public abstract void doPost(GlRequest request, GlResponse response) throws Exception;
}
```
## FirstServlet 具体的业务Servlet实现抽象Servlet的方法
```text
public class FirstServlet extends GlServlet {
@Override
public void doGet(GlRequest request, GlResponse response) throws Exception {
// 具体的逻辑
this.doPost(request, response);
}
@Override
public void doPost(GlRequest request, GlResponse response) throws Exception {
response.write("This is first servlet from NIO");
}
}
```
## SecondServlet 具体的业务Servlet实现抽象Servlet方法
```text
public class SecondServlet extends GlServlet {
@Override
public void doGet(GlRequest request, GlResponse response) throws Exception {
doPost(request,response);
}
@Override
public void doPost(GlRequest request, GlResponse response) throws Exception {
response.write("This second request form NIO");
}
}
```
## web-nio.properties 配置文件
> 配置请求和处理器,Spring中是通过Controller下的@XXXMapping注解去扫描并加载到工厂的;
```text
servlet.one.className=com.ibli.netty.tomcat.nio.servlet.FirstServlet
servlet.one.url=/firstServlet.do
servlet.two.className=com.ibli.netty.tomcat.nio.servlet.SecondServlet
servlet.two.url=/secondServlet.do
```
## GlTomcat
> 启动服务端,在网页中访问本地8080端口,输入配置文件中定义的url进行测试:
```text
public class GlTomcat {
private final Integer PORT = 8080;
private Properties webXml = new Properties();
private Map<String, GlServlet> servletMapping = new HashMap<String, GlServlet>();
public static void main(String[] args) {
new GlTomcat().start();
}
/**
* Tomcat的启动入口
*/
private void start() {
//1、加载web配置文件,解析配置
init();
// Boss线程
EventLoopGroup bossGroup = new NioEventLoopGroup();
// Worker线程
EventLoopGroup workGroup = new NioEventLoopGroup();
//2、创建Netty服务端对象
ServerBootstrap server = new ServerBootstrap();
//3、 配置服务端参数
server.group(bossGroup, workGroup)
// 配置主线程的处理逻辑
.channel(NioServerSocketChannel.class)
// 子线程的回调逻辑
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel client) {
// 处理具体的回调逻辑
// 责任链模式
//返回-编码
client.pipeline().addLast(new HttpResponseEncoder());
//请求-解码
client.pipeline().addLast(new HttpRequestDecoder());
//用户自己的逻辑处理
client.pipeline().addLast(new GlTomcatHandler());
}
})
// 配置主线程可分配的最大线程数
.option(ChannelOption.SO_BACKLOG, 128)
//保持长链接
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = null;
try {
future = server.bind(this.PORT).sync();
System.err.println("Gl tomcat started in pory " + this.PORT);
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
/**
* 加载配置文件
* 这其实使用了策略模式
*/
private void init() {
try {
String WEB_INF = this.getClass().getResource("/").getPath();
FileInputStream fis = new FileInputStream(WEB_INF + "web-nio.properties");
webXml.load(fis);
for (Object k : webXml.keySet()) {
String key = k.toString();
if (key.endsWith(".url")) {
//servlet.two.url
String servletName = key.replaceAll("\\.url", "");
String url = webXml.getProperty(key);
//servlet.two.className
String className = webXml.getProperty(servletName + ".className");
//反射创建servlet实例
// load-on-startup >=1 :web启动的时候初始化 0:用户请求的时候才启动
GlServlet obj = (GlServlet) Class.forName(className).newInstance();
// 将url和servlet建立映射关系
servletMapping.put(url, obj);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理用户请求
*/
public class GlTomcatHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
GlRequest request = new GlRequest(ctx,req);
GlResponse response = new GlResponse(ctx,req);
String url = request.getUrl();
if (servletMapping.containsKey(url)){
servletMapping.get(url).service(request,response);
}
else {
response.write("404 Not Fount");
}
}
}
}
}
```
## 测试结果
> 请求 : http://localhost:8080/secoundServlet.do 这的地址写错误 ⚠️

> 请求 : http://localhost:8080/secondServlet.do
