一.什么是servlet
为防有些读者忘却了当时大学老师在讲J2EE的场景,简单做个回忆杀. servlet 是JavaEE的web规范之一,简单来说就是一个定义了处理网络请求规范.而具体到代码来说是个接口.定义了这样几个方法:
- init
- destory
- service
- getServletConfig
- getServletInfo
那就是说所有[JAVA]来处理网络请求的家伙都需要继承实现我,不然你什么网络请求都别干了.
那就是如下这张图.
1.1 为什么要定义标准接口
自己可以不可以写,可以,每个人都可以定义一个标准和方法.但是这样下去每个人都可能会定义一个标准和规范.反过来就不是标准,也不利于整个技术的演进和维护. 所以大家都遵从JavaEE定义的接口标准进行实现,那么就有了响应的厂商,诸如tomcat,jetty等web容器厂商实现的servlet容器了.
二.如何自己写一个tomcat servlet容器
tomcat基本的工作流程是这样.
1)接收到请求传递给Servlet容器 3)Servlet容器通过已经装配好的servlet获取指定的请求servlet实例 4)Servlet实例获取相应的请求信息进行业务处理 5)Servlet实例处理业务结果返回给响应对象
上面了解了基本的tomcat处理servlet流程.那么我们照葫芦画瓢,开干.
2.1 定义servlet
package com.craftsman.tomcat.tradition;
import com.craftsman.tomcat.nio.BXNIORequest;
import com.craftsman.tomcat.nio.BXNIOResponse;
/**
* 抽象tomcat servlet
* @author chenfanglin
* @date 2021年05月16日
*/
public abstract class BXServlet {
public void service(BXRequest request, BXResponse response)throws Exception{
if("GET".equalsIgnoreCase(request.getMethod())){
doGet(request,response);
}else if("POST".equalsIgnoreCase(request.getMethod())){
doPost(request,response);
}
}
public abstract void doGet(BXRequest request,BXResponse response)throws Exception;
public abstract void doPost(BXRequest request,BXResponse response)throws Exception;
}
如上定义了servlet 的service方法,此处省略了init destory等方法.
2.2 具体业务实现
package com.craftsman.tomcat.tradition;
public class FirstServlet extends BXServlet {
public void doGet(BXRequest request, BXResponse response) throws Exception {
doPost(request,response);
}
public void doPost(BXRequest request, BXResponse response) throws Exception {
response.write("this is bx first servlet");
}
}
如上实现了servlet协议,定义了业务处理逻辑.doPost方法.
2.3 tomcat 启动类
package com.craftsman.tomcat.tradition;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class BXTomcat {
private int port=8080;
private ServerSocket SERVER;
//servlet 映射.类似于springmvc的路由分发器
private Map<String,BXServlet> SERVLET_MAP =new HashMap<String,BXServlet>();
private Properties WEB_XML =new Properties();
private void init(){
//读取web.properties资源的值
String webInf=this.getClass().getResource("/").getPath();
try {
FileInputStream fis=new FileInputStream(webInf+"web.properties");
WEB_XML.load(fis);
for(Object k: WEB_XML.keySet()){
String key=k.toString();
if(key.endsWith(".url")){
String servletName=key.replaceAll("\\.url$","");
String url= WEB_XML.getProperty(key);
String className= WEB_XML.getProperty(servletName+".className");
BXServlet obj=(BXServlet) Class.forName(className).newInstance();
//加载固定的url与对应的处理器.url不能重复
SERVLET_MAP.put(url,obj);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void start(){
//1.初始化servlet
init();
try {
//2.启动监听端口
SERVER =new ServerSocket(this.port);
System.out.println("bx tomcat 已启动 端口:"+this.port);
//3.死循环用以接收用户请求
while (true){
Socket client= SERVER.accept();
process(client);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void process(Socket client)throws Exception{
//1.获取输入输出流
InputStream is=client.getInputStream();
OutputStream os=client.getOutputStream();
//2.获取对应request response 对象
BXRequest request=new BXRequest(is);
BXResponse response=new BXResponse(os);
//3.处理请求request 并进行分发处理.
String url=request.getUrl();
if(SERVLET_MAP.containsKey(url)){
SERVLET_MAP.get(url).service(request,response);
}else{
response.write("this url is not founded");
}
os.flush();
os.close();
is.close();
client.close();
}
public static void main(String[] args) {
new BXTomcat().start();
System.out.println("this bx tomcat already started");
}
}
如上初始化容器,准备url分发器,接收前端请求.
三.netty 改造现有的传统模式
netty有天然支持http协议解码和编码的优势,尝试使用netty的方法来改造现有的tomcat容器.
直接来看现有的tomcat容器部分
3.1 tomcat容器改造
package com.craftsman.tomcat.nio;
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;
/**
* 用以改造既有传统socket方式tomcat servlet 容器
* @author chenfanglin
* @date 2021年05月16日
*/
public class BXNIOTomcat {
private int port=8081;
private Map<String,BXNIOServlet> SERVLET_MAP =new HashMap<String,BXNIOServlet>();
private Properties WEB_XML =new Properties();
private void init(){
String webInf=this.getClass().getResource("/").getPath();
try {
FileInputStream fis=new FileInputStream(webInf+"web-nio.properties");
WEB_XML.load(fis);
for(Object k: WEB_XML.keySet()){
String key=k.toString();
if(key.endsWith(".url")){
String servletName=key.replaceAll("\\.url$","");
String url= WEB_XML.getProperty(key);
String className= WEB_XML.getProperty(servletName+".className");
BXNIOServlet obj=(BXNIOServlet) Class.forName(className).newInstance();
SERVLET_MAP.put(url,obj);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void start(){
//1.初始化servlet
init();
//定义用以处理请求的主线程和子线程,都是子线程在做事
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
//2.启动nio对象bootstrap
ServerBootstrap serverBootstrap=new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
// 处理http协议的解码器和编码器
ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpRequestDecoder());
//业务处理器
ch.pipeline().addLast(new BXTomcatHandler());
}
})
//设置主线程最大线程数
.option(ChannelOption.SO_BACKLOG,128)
//子线程保持长链接
.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture f=serverBootstrap.bind(this.port).sync();
System.out.println("bx nio tomcat 已经启动 监听的端口:"+this.port);
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
//业务处理器
public class BXTomcatHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof HttpRequest){
System.out.println("接收到前端请求msg"+msg);
HttpRequest request=(HttpRequest) msg;
BXNIORequest realRequest=new BXNIORequest(ctx,request);
BXNIOResponse realResponse=new BXNIOResponse(ctx,request);
String url=realRequest.getRequest().uri();
if(SERVLET_MAP.containsKey(url)){
//分发业务请求
SERVLET_MAP.get(url).service(realRequest,realResponse);
}else{
realResponse.write("nio tomcat not found request url");
}
}
}
}
public static void main(String[] args) {
new BXNIOTomcat().start();
System.out.println("this bx nio tomcat already started");
}
}
踩坑: 小问题 在获取resources路径下配置文件时,发现resources文件中不会被打包进入target文件.
最后通过在pom文件添加对应resources路径以及对应的maven插件后解决.再次记录下.
<build>
<resources>
<resource>
<directory>src/main/java</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
</plugin>
</plugins>
</build>
具体代码参见: