总体思路
以往部署servlet项目都是部署在tomcat上,并且有一个全局的web.xml配置实现的servlet和filter过滤器,在这里我们自定义一个web.properties来代替web.xml;除此之外,我们需要做的呀Request和Response来处理http请求和响应,也要自定义servlet来处理http请求的相应后端业务逻辑,servlet与url的映射关系我们配置在web.properties中。
1、环境准备
自定义Servlet
servlet抽象类
public abstract class AbstractServlet {
public void service(MyRequest request, MyResponse response) throws Exception {
if ("GET".equals(request.getMethod())) {
doGet(request, response);
} else {
doPost(request, response);
}
}
public abstract void doGet(MyRequest request, MyResponse response) throws Exception;
public abstract void doPost(MyRequest request, MyResponse response) throws Exception;
}
servlet实现类MyFirstServlet
public class MyFirstServlet extends AbstractServlet {
@Override
public void doGet(MyRequest request, MyResponse response) throws Exception {
this.doPost(request, response);
}
@Override
public void doPost(MyRequest request, MyResponse response) throws Exception {
response.write("MyFirstServlet done!");
}
}
servlet实现类MySecondServlet
public class MySecondServlet extends AbstractServlet {
@Override
public void doGet(MyRequest request, MyResponse response) throws Exception {
this.doPost(request, response);
}
@Override
public void doPost(MyRequest request, MyResponse response) throws Exception {
response.write("MySecondServlet done!");
}
}
2、自定义配置文件web.properties
servlet.first.url=/first
servlet.first.className=com.mxd.nettystudy.tomcat.servlet.MyFirstServlet
servlet.second.url=/second
servlet.second.className=com.mxd.nettystudy.tomcat.servlet.MySecondServlet
3、基于传统I/O实现Tomcat
自定义Request=>MyRequest
public class MyRequest {
private String method;
private String url;
public MyRequest(InputStream is) {
try {
String content = "";
byte[] bytes = new byte[1024];
int len = 0;
if ((len = is.read(bytes)) > 0) {
content = new String(bytes, 0 ,len);
}
// 这里相当于是 "GET /second HTTP/1.1"
String line = content.split("\\n")[0];
String[] split = line.split("\\s");
this.method = split[0];
this.url = split[1].split("\\s")[0];
} catch (IOException e) {
e.printStackTrace();
}
}
public String getUrl() {
return url;
}
public String getMethod() {
return method;
}
}
Http请求内容参考
GET /second HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
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/99.0.4844.83 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
自定义Response=>MyResponse
public class MyResponse {
private OutputStream os;
public MyResponse(OutputStream os) {
this.os = os;
}
public void write(String msg) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 200 OK\n")
.append("Content-Type: text/html;\n")
.append("\r\n")
.append(msg);
os.write(sb.toString().getBytes());
}
}
自定义Tomcat启动类
public class TomcatApplication {
private int port = 8080;
private ServerSocket serverSocket;
// Map存储url与servlet的映射
private Map<String, AbstractServlet> servletMapping = new HashMap<>();
// 存储web.properties配置文件
private Properties webProperties = new Properties();
// 容器启动时加载配置文件
public void init() {
try {
String path = this.getClass().getResource("/").getPath();
FileInputStream fis = new FileInputStream(path + "web.properties");
webProperties.load(fis);
for (Object k : webProperties.keySet()) {
String key = k.toString();
if (key.endsWith(".url")) {
String url = webProperties.getProperty(key);
String className = webProperties.getProperty(key.replaceAll("\\.url$", "") + ".className");
AbstractServlet servlet = (AbstractServlet) Class.forName(className).newInstance();
servletMapping.put(url, servlet);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void start(){
init();
try {
serverSocket = new ServerSocket(this.port);
System.out.println("自定义tomcat启动-----监听端口:" + this.port);
while (true) {
// 监听端口
Socket accept = serverSocket.accept();
System.out.println(accept.getRemoteSocketAddress());
// 处理请求
process(accept);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void process(Socket accept) throws Exception {
InputStream is = accept.getInputStream();
OutputStream os = accept.getOutputStream();
MyRequest request = new MyRequest(is);
MyResponse response = new MyResponse(os);
String url = request.getUrl();
// 处理url
if (servletMapping.containsKey(url)) {
servletMapping.get(url).service(request, response);
} else {
response.write("404-Not Found");
}
os.flush();
os.close();
is.close();
accept.close();
}
public static void main(String[] args) {
// 启动tomcat服务
new TomcatApplication().start();
}
}
4、基于Netty实现Tomcat
重构Request=>MyRequest
public class MyRequest {
private ChannelHandlerContext ctx;
private HttpRequest request;
public MyRequest(ChannelHandlerContext ctx, HttpRequest request) {
this.ctx = ctx;
this.request = request;
}
public String getUrl() {
return this.request.uri();
}
public String getMethod() {
return this.request.method().name();
}
// 获取指定参数值
public String getParameter(String paramName) {
Map<String, List<String>> parameters = getParameters();
List<String> paramValues = parameters.get(paramName);
return Objects.nonNull(paramValues)? paramValues.get(0): null;
}
// 解析http的URL请求中所有参数键值对
public Map<String, List<String>> getParameters() {
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(this.request.uri());
return queryStringDecoder.parameters();
}
}
重构Response=>MyResponse
public class MyResponse {
private ChannelHandlerContext ctx;
private HttpRequest request;
public MyResponse(ChannelHandlerContext ctx, HttpRequest request) {
this.ctx = ctx;
this.request = request;
}
public void write(String msg) throws IOException {
if (!StringUtils.hasText(msg)) return;
try {
FullHttpResponse response = new DefaultFullHttpResponse(
// 设置版本为HTTP 1.1
HttpVersion.HTTP_1_1,
// 设置状态响应码
HttpResponseStatus.OK,
// 设置输出内容编码格式
Unpooled.wrappedBuffer(msg.getBytes(HttpConstants.DEFAULT_CHARSET))
);
response.headers().set("Content-Type", "text/html");
ctx.write(response);
} finally {
ctx.flush();
ctx.close();
}
}
}
重构Tomcat启动类[netty实现]
public class TomcatApplication {
private int port = 8080;
private ServerSocket serverSocket;
private Map<String, AbstractServlet> servletMapping = new HashMap<>();
private Properties webProperties = new Properties();
// 容器启动时加载配置文件
public void init() {
try {
String path = this.getClass().getResource("/").getPath();
FileInputStream fis = new FileInputStream(path + "web.properties");
webProperties.load(fis);
for (Object k : webProperties.keySet()) {
String key = k.toString();
if (key.endsWith(".url")) {
String url = webProperties.getProperty(key);
String className = webProperties.getProperty(key.replaceAll("\\.url$", "") + ".className");
AbstractServlet servlet = (AbstractServlet) Class.forName(className).newInstance();
servletMapping.put(url, servlet);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void start(){
init();
// netty封装的Reactor模型
// boss线程
NioEventLoopGroup boss = new NioEventLoopGroup();
// work线程
NioEventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap server = new ServerBootstrap();
try {
server.group(boss, work)
// 主线程处理
.channel(NioServerSocketChannel.class)
// 子线程处理
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// netty对http的封装,顺序有要求
ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpRequestDecoder());
// 业务逻辑处理
MyTomcatHandler tomcatHandler = new MyTomcatHandler();
tomcatHandler.setServletMapping(servletMapping);
ch.pipeline().addLast(tomcatHandler);
}
})
// 主线程配置 最大线程数量128
.option(ChannelOption.SO_BACKLOG, 128)
// 子线程配置 保持长链接
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture channelFuture = server.bind(port).sync();
System.out.println("自定义tomcat启动-----端口:" + port);
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 关闭线程池
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
public static void main(String[] args) {
// 启动tomcat服务
new TomcatApplication().start();
}
}
实现业务处理的handler
public class MyTomcatHandler extends ChannelInboundHandlerAdapter {
private Map<String, AbstractServlet> servletMapping;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
HttpRequest request = (HttpRequest) msg;
MyRequest myRequest = new MyRequest(ctx, request);
MyResponse myResponse = new MyResponse(ctx, request);
String url = myRequest.getUrl();
if (servletMapping.containsKey(url)) {
servletMapping.get(url).service(myRequest, myResponse);
} else {
myResponse.write("404-Not Found");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
}
public Map<String, AbstractServlet> getServletMapping() {
return servletMapping;
}
public void setServletMapping(Map<String, AbstractServlet> servletMapping) {
this.servletMapping = servletMapping;
}
}
5、启动Tomcat,运行演示
这里我们启动基于Netty实现的Tomcat,所以直接运行Tomcat的启动类中的main方法new TomcatApplication().start();
控制台效果
浏览器请求效果
浏览器输入=》http://localhost:8080/first
浏览器输入=》http://localhost:8080/second