前言
前章
【手写一个Tomcat】SimpleTomcat-01
本文基于netty改进简易Tomcat,遵循【Tomcat】第九篇:使用 Netty 重构…
配置
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SimpleTomcat</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>SimpleTomcat-01</module>
<module>SimpleTomcat-02</module>
</modules>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
</dependencies>
</project>
resource.web.properties
web.properties
servlet.one.url=/firstServlet
servlet.one.className=com.sample.servlet.FirstServlet
servlet.two.url=/secondServlet
servlet.two.className=com.sample.servlet.SecondServlet
实现
http.TomcatRequest
TomcatRequest.java
package com.sample.http;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
/**
* TomcatRequest
* @author xxx
*/
public class TomcatRequest {
private ChannelHandlerContext channelHandlerContext;
private HttpRequest request;
public TomcatRequest(ChannelHandlerContext channelHandlerContext, HttpRequest request) {
this.channelHandlerContext = channelHandlerContext;
this.request = request;
}
/**
* 返回url
* @return String
*/
public String getUrl() {
return request.uri();
}
/**
* 返回请求方法
* @return String
*/
public String getMethod() {
return request.method().name();
}
public Map<String, List<String>> getParameters() {
QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
return decoder.parameters();
}
public String getParameter(String name) {
Map<String, List<String>> params = getParameters();
List<String> param = params.get(name);
if (null == param) {
return null;
}
else {
return param.get(0);
}
}
}
http.TomcatResponse
TomcatResponse.java
package com.sample.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
/**
* TomcatResponse
* @author xxx
*/
public class TomcatResponse {
/**
* SocketChannel的封装
*/
private ChannelHandlerContext channelHandlerContext;
private HttpRequest request;
public TomcatResponse(ChannelHandlerContext channelHandlerContext, HttpRequest request) {
this.channelHandlerContext = channelHandlerContext;
this.request = request;
}
public void write(String out) throws IOException {
try {
if (out == null || out.length() == 0) {
return;
}
// 设置http协议和请求头信息
FullHttpResponse response = new DefaultFullHttpResponse(
// 设置http版本为1.1
HttpVersion.HTTP_1_1,
// 设置响应状态码
HttpResponseStatus.OK,
// 将输出值写出 编码为UTF-8
Unpooled.wrappedBuffer(out.getBytes("UTF-8"))
);
// 设置Content-Type
response.headers().set("Content-Type", "text/html;");
// 是否设置支持长连接
// if (HttpUtil.isKeepAlive(r)) {
// // 设置连接内容为长连接
// response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
// }
channelHandlerContext.write(response);
} finally {
channelHandlerContext.flush();
channelHandlerContext.close();
}
}
}
http.TomcatServlet
TomcatServlet.java
package com.sample.http;
import java.io.IOException;
public abstract class TomcatServlet {
/**
* 这里的request与response都是Tomcat对象创建好然后传进来的
* @param tomcatRequest
* @param tomcatResponse
* @throws Exception
*/
public void service(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws Exception {
if ("GET".equalsIgnoreCase(tomcatRequest.getMethod())) {
doGet(tomcatRequest, tomcatResponse);
}
else {
doPost(tomcatRequest, tomcatResponse);
}
}
/**
* 这里是模板方法模式,交给子类去具体实现
* @param tomcatRequest
* @param tomcatResponse
* @throws Exception
*/
protected abstract void doPost(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws Exception;
/**
*
* @param tomcatRequest
* @param tomcatResponse
* @throws Exception
*/
protected abstract void doGet(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws Exception;
}
servlet.FirstServlet
FirstServlet.java
package com.sample.servlet;
import com.sample.http.TomcatRequest;
import com.sample.http.TomcatResponse;
import com.sample.http.TomcatServlet;
import java.io.IOException;
public class FirstServlet extends TomcatServlet {
@Override
protected void doPost(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
tomcatResponse.write("this is FirstServlet!");
}
@Override
protected void doGet(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
doPost(tomcatRequest, tomcatResponse);
}
}
servlet.SecondServlet
SecondServlet.java
package com.sample.servlet;
import com.sample.http.TomcatRequest;
import com.sample.http.TomcatResponse;
import com.sample.http.TomcatServlet;
import java.io.IOException;
public class SecondServlet extends TomcatServlet {
@Override
protected void doPost(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
tomcatResponse.write("Hello world!");
}
@Override
protected void doGet(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
doPost(tomcatRequest, tomcatResponse);
}
}
SimpleTomcat
SimpleTomcat.java
package com.sample;
import com.sample.http.TomcatRequest;
import com.sample.http.TomcatResponse;
import com.sample.http.TomcatServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
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 java.io.FileInputStream;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* Tomcat核心类
*/
public class SimpleTomcat {
private int port = 8080;
private ServerSocket serverSocket;
/**
* 用来保存路径与Servlet的映射关系(servlet单例模式)
*/
private Map<String, TomcatServlet> servletMapping = new HashMap<>();
private Properties webxml = new Properties();
/**
* 加载web.xml文件,同时初始化 ServletMapping对象
*/
private void init() {
try {
// 1.加载web.properties文件
String WEB_INF = this.getClass().getResource("/").getPath();
FileInputStream fileInputStream = new FileInputStream(WEB_INF + "web.properties");
webxml.load(fileInputStream);
// 2.遍历配置文件,寻找url与servlet映射关系配置
for (Object o : webxml.keySet()) {
String key = o.toString();
// 以url结尾的key就是要映射的路径,下面是两条配置示例:
// servlet.one.url=/firstServlet.do
// servlet.one.className=com.yzh.tomcat.servlet.FirstServlet
if (key.endsWith(".url")) {
// 去掉.url就是servlet的name(servlet.one)
String servletName = key.replaceAll("\\.url$", "");
// 2.1 获取到url(/first.do)
String url = webxml.getProperty(key);
// 2.2 获取对应servlet全类名(com.sample.servlet...FirstServlet),并通过反进行实例化
String className = webxml.getProperty(servletName + ".className");
// 注:这里是将所有Servlet都强转为TomcatServlet,所以一定要继承TomcatServlet
TomcatServlet tomcatServlet = (TomcatServlet) Class.forName(className).newInstance();
// 3.将url与servlet实例保存到servletMapping中(单例模式)
servletMapping.put(url, tomcatServlet);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 启动tomcat
* 1. 调用init方法,加载web.xml
* 2.等待用户请求,并对每个请求进行处理
*/
public void start() {
// 1.调用init,目的是得到servletMapping的映射关系
init();
// netty封装了nio,Reactor模型,Boss,worker
// Boss线程
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// Worker线程
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// Netty服务
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 链路式编程
serverBootstrap.group(bossGroup, workerGroup)
// 主线程处理类,看到这样的写法,底层就是用反射
.channel(NioServerSocketChannel.class)
// 子线程处理类Handler
.childHandler(new ChannelInitializer<SocketChannel>() {
// 客户端初始化处理
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 无锁化串行编程,Netty对HTTP协议的封装,顺序有要求
// HttpResponseEncoder 编码器
socketChannel.pipeline().addLast(new HttpResponseEncoder());
// HttpRequestDecoder 解码器
socketChannel.pipeline().addLast(new HttpRequestDecoder());
// 业务逻辑处理
socketChannel.pipeline().addLast(new SimpleTomcatHandler());
}
})
// 针对主线程的配置 分配线程最大数量 128
.option(ChannelOption.SO_BACKLOG, 128)
// 针对子线程的配置 保持长连接
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
// 启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
System.out.println("SimpleTomcat 已启动,监听的端口是:" + port);
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}
}
public class SimpleTomcatHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest httpRequest = (HttpRequest) msg;
// 转交给我们自己的TomcatRequest实现
TomcatRequest tomcatRequest = new TomcatRequest(channelHandlerContext, httpRequest);
// 转交给我们自己的TomcatResponse实现
TomcatResponse tomcatResponse = new TomcatResponse(channelHandlerContext, httpRequest);
// 实际业务处理
String url = tomcatRequest.getUrl();
if (servletMapping.containsKey(url)) {
servletMapping.get(url).service(tomcatRequest, tomcatResponse);
}
else {
tomcatResponse.write("404 - Not Found");
}
}
}
}
public static void main(String[] args) {
new SimpleTomcat().start();
}
}
运行
运行 SimpleTomcat.java
显示
SimpleTomcat 已启动,监听的端口是:8080
之后我们可以访问:
http://localhost:8080/secondServlet
或
http://localhost:8080/firstServlet
|