基于BIO、Netty手写简易Tomcat
源码地址:https://gitee.com/zy4350/simpleTomcat.git
Tomcat简介
- 什么是tomcat
- tomcat工作原理
基于BIO实现SimpleTomCat
定义接口规范
-
定义Servlet抽象类
/** * @author zheng yong * @date 2022/10/13、13:25 */ public abstract class Servlet { public void service(Request request, Response response) throws Exception { if ("GET".equalsIgnoreCase(request.getMethod())) { doGet(request, response); } else { doPost(request, response); } } public abstract void doGet(Request request, Response response) throws IOException; public abstract void doPost(Request request, Response response) throws IOException; }
-
定义Request、Response接口
/** * @author zheng yong * @date 2022/10/13、13:26 */ public interface Request { String getUrl(); String getMethod(); } public interface Response { void write(String resposenMeg) throws IOException; }
Tomcat 代码实现
-
BIOTomcat实现
public class BIOTomcat { private int port = 8080; private ServerSocket server; private Properties webxml = new Properties(); // servlet 容器 private Map<String, Servlet> servletContainer = new ConcurrentHashMap<>(); // tomcat 启动入口 public void start() throws Exception { // 1、 加载web.properties(web.xml)文件,解析配置 init(); // 2、启动服务端socket,等待用户请求 server = new ServerSocket(this.port); System.out.println("simple bio tomcat is started. this port:" + this.port); while (true) { Socket client = server.accept(); // 3、获得请求信息,解析http协议的内容 process(client); } } // 1、初始化配置信息 private void init() throws Exception { // 加载配置文件 String WEB_INF = this.getClass().getResource("/").getPath(); FileInputStream fis = new FileInputStream(WEB_INF + "web.properties"); // webxml.load(fis); for (Object k : webxml.keySet()) { String key = k.toString(); if (key.endsWith(".url")) { String sevlectName = key.replaceAll("\\.url$", ""); String url = webxml.getProperty(key); String className = webxml.getProperty(sevlectName + ".className"); Servlet servlet = (Servlet) Class.forName(className).newInstance(); servletContainer.put(url, servlet); } } } private void process(Socket client) throws Exception { InputStream is = client.getInputStream(); OutputStream os = client.getOutputStream(); Request request = new BIORequest(is); Response response = new BIOResponse(os); String url = request.getUrl(); if (servletContainer.containsKey(url)) { servletContainer.get(url).service(request, response); } else { response.write("404 - Not Found."); } os.flush(); os.close(); is.close(); client.close(); } }
-
BIO实现Request
package org.yongz.tomcat.bio.http; import org.yongz.tomcat.Interface.Request; import java.io.IOException; import java.io.InputStream; /** * @author zheng yong * @date 2022/10/13、13:24 */ public class BIORequest implements Request { private String url; private String method; public BIORequest(InputStream inputStream) throws IOException { // 打印http协议的具体内容 String httpContent = ""; byte[] buff = new byte[1024]; int len = 0; if ((len = inputStream.read(buff)) > 0) { httpContent += new String(buff, 0, len); } System.out.println(httpContent); // 解析出url\请求类型 String httpHeadLine = httpContent.split("\\n")[0]; String[] arr = httpHeadLine.split("\\s"); this.url = arr[1].split("\\?")[0]; this.method = arr[0]; } @Override public String getUrl() { return this.url; } @Override public String getMethod() { return this.method; } }
-
BIO实现Response
package org.yongz.tomcat.bio.http; import org.yongz.tomcat.Interface.Response; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; /** * @author zheng yong * @date 2022/10/13、13:25 */ public class BIOResponse implements Response { private OutputStream os; public BIOResponse(OutputStream os) { this.os = os; } @Override public void write(String resposenMeg) throws IOException { StringBuffer sb = new StringBuffer(); sb.append("HTTP/1.1 200 ok\n") .append("Content-Type:text/html;\n") .append("\r\n") .append(resposenMeg); os.write(sb.toString().getBytes(StandardCharsets.UTF_8)); } }
-
编写Servlet以及配置文件web.properties(同tomcat的web.xml)
/** * @author zheng yong * @date 2022/10/13、13:30 */ public class OneServlet extends Servlet { @Override public void doGet(Request request, Response response) throws IOException { doPost(request, response); } @Override public void doPost(Request request, Response response) throws IOException { response.write("this is oneSevlet for BIO."); } }
web.properties
servlet.one.className=org.yongz.tomcat.bio.servlet.OneServlet servlet.one.url=/oneServlet.do
-
测试
基于Netty(NIO)实现SimpleTomCat
接口规范BIO接口规范定义
- 环境准备
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
-
NettyTomcat实现
package org.yongz.tomcat.nio; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; import org.yongz.tomcat.Interface.Servlet; import org.yongz.tomcat.nio.http.NIORequest; import org.yongz.tomcat.nio.http.NIOResponse; import java.io.FileInputStream; import java.net.ServerSocket; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; /** * @author zheng yong * @date 2022/10/13、13:33 */ public class NIOTomcat { private int port = 8080; private ServerSocket server; private Properties webxml = new Properties(); // servlet 容器 private Map<String, Servlet> servletContainer = new ConcurrentHashMap<>(); // boss线程 private NioEventLoopGroup bossGroup = new NioEventLoopGroup(); // worker线程 private NioEventLoopGroup workerGroup = new NioEventLoopGroup(); // tomcat 启动入口 public void start() { try { // 1、 加载web.properties(web.xml)文件,解析配置 init(); // 2、创建netty服务器对象 ServerBootstrap server = new ServerBootstrap(); // boss线程 bossGroup = new NioEventLoopGroup(); // worker线程 workerGroup = new NioEventLoopGroup(); // 3、 配置服务端参数 server.group(bossGroup, workerGroup) // 配置主线程的处理逻辑 .channel(NioServerSocketChannel.class) // 子线程的回调处理,Handler .childHandler(new ChannelInitializer() { @Override protected void initChannel(Channel client) throws Exception { // 处理回调的逻辑 // 责任链模式 // 处理响应结果的封装 client.pipeline().addLast(new HttpResponseEncoder()); // 用户请求过来,要解码 client.pipeline().addLast(new HttpRequestDecoder()); // 用户自己的业务逻辑 client.pipeline().addLast(new NIOTomcatHandler()); } }) // 配置主线程分配的最大线程数 .option(ChannelOption.SO_BACKLOG, 128) // 保持长连接 .childOption(ChannelOption.SO_KEEPALIVE, true); // 启动服务,绑定端口 ChannelFuture future = server.bind(this.port).sync(); System.out.println("simple nio tomcat is started. this port:" + this.port); future.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } // 1、初始化配置信息 private void init() throws Exception { // 加载配置文件 String WEB_INF = this.getClass().getResource("/").getPath(); FileInputStream fis = new FileInputStream(WEB_INF + "web.properties"); // webxml.load(fis); for (Object k : webxml.keySet()) { String key = k.toString(); if (key.endsWith(".url")) { String sevlectName = key.replaceAll("\\.url$", ""); String url = webxml.getProperty(key); String className = webxml.getProperty(sevlectName + ".className"); Servlet servlet = (Servlet) Class.forName(className).newInstance(); servletContainer.put(url, servlet); } } } /** * @author zheng yong * @date 2022/10/13、15:51 */ public class NIOTomcatHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpRequest) { HttpRequest req = (HttpRequest) msg; NIORequest request = new NIORequest(ctx, req); NIOResponse response = new NIOResponse(ctx, req); String url = request.getUrl(); if (servletContainer.containsKey(url)) { Servlet servlet = servletContainer.get(url); servlet.service(request, response); } else { response.write("404 - Not found."); } } } } }
-
NIO实现Request
package org.yongz.tomcat.nio.http; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpRequest; import org.yongz.tomcat.Interface.Request; /** * @author zheng yong * @date 2022/10/13、13:24 */ public class NIORequest implements Request { private ChannelHandlerContext ctx; private HttpRequest req; public NIORequest(ChannelHandlerContext ctx, HttpRequest req) { this.ctx = ctx; this.req = req; } @Override public String getUrl() { return this.req.getUri(); } @Override public String getMethod() { return this.req.getMethod().name(); } }
-
NIO实现Response
package org.yongz.tomcat.nio.http;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledDirectByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.util.internal.StringUtil;
import org.yongz.tomcat.Interface.Response;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @author zheng yong
* @date 2022/10/13、13:25
*/
public class NIOResponse implements Response {
private ChannelHandlerContext ctx;
private HttpRequest req;
public NIOResponse(ChannelHandlerContext ctx, HttpRequest req) {
this.ctx = ctx;
this.req = req;
}
@Override
public void write(String resposenMeg) throws IOException {
try {
if (StringUtil.isNullOrEmpty(resposenMeg)) {
return;
}
DefaultFullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(resposenMeg.getBytes(StandardCharsets.UTF_8))
);
response.headers().set("Content-Type","text/html");
ctx.write(response);
} catch (Exception e) {
e.printStackTrace();
} finally {
ctx.flush();
ctx.close();
}
}
}
-
编写Servlet以及配置文件web.properties(同tomcat的web.xml)
package org.yongz.tomcat.nio.servlet; import org.yongz.tomcat.Interface.Request; import org.yongz.tomcat.Interface.Response; import org.yongz.tomcat.Interface.Servlet; import java.io.IOException; /** * @author zheng yong * @date 2022/10/13、13:30 */ public class OneNIOServlet extends Servlet { @Override public void doGet(Request request, Response response) throws IOException { doPost(request, response); } @Override public void doPost(Request request, Response response) throws IOException { response.write("this is OneNIOServlet for NIO."); } }
web.properties
servlet.two.nio.className=org.yongz.tomcat.nio.servlet.OneNIOServlet servlet.two.nio.url=/oneNIOServlet.do
-
测试