实现简单的Tomcat | Tomcat原理学习(1)

3次阅读

共计 7428 个字符,预计需要花费 19 分钟才能阅读完成。

缘起
用了那么久 tomcat,突然觉得自己对它或许并没有想象中的那么熟悉,所以趁着放假我研究了一下这只小猫咪,实现了自己的小 tomcat,写出这篇文章同大家一起分享!
照例附上 github 链接。
项目结构
项目结构如下:

实现细节
创建 MyRequest 对象
首先创建自定义的请求类,其中定义 url 与 method 两个属性,表示请求的 url 以及请求的方式。
其构造函数需要传入一个输入流,该输入流通过客户端的套接字对象得到。
输入流中的内容为浏览器传入的 http 请求头,格式如下:
GET /student HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: Hm_lvt_eaa22075ffedfde4dc734cdbc709273d=1549006558; _ga=GA1.1.777543965.1549006558

通过对以上内容的分割与截取,我们可以得到该请求的 url 以及请求的方式。
package tomcat.dome;

import java.io.IOException;
import java.io.InputStream;

// 实现自己的请求类
public class MyRequest {
// 请求的 url
private String url;
// 请求的方法类型
private String method;

// 构造函数 传入一个输入流
public MyRequest(InputStream inputStream) throws IOException {
// 用于存放 http 请求内容的容器
StringBuilder httpRequest=new StringBuilder();
// 用于从输入流中读取数据的字节数组
byte[]httpRequestByte=new byte[1024];
int length=0;
// 将输入流中的内容读到字节数组中,并且对长度进行判断
if((length=inputStream.read(httpRequestByte))>0) {
// 证明输入流中有内容,则将字节数组添加到容器中
httpRequest.append(new String(httpRequestByte,0,length));
}
// 将容器中的内容打印出来
System.out.println(“httpRequest = [ “+httpRequest+”]”);

// 从 httpRequest 中获取 url,method 存储到 myRequest 中
String httpHead=httpRequest.toString().split(“\n”)[0];
url=httpHead.split(“\\s”)[1];
method=httpHead.split(“\\s”)[0];
System.out.println(“MyRequests = [ “+this+”]”);
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public String getMethod() {
return method;
}

public void setMethod(String method) {
this.method = method;
}

}

创建 MyResponse 对象
创建自定义的响应类,构造函数需要传入由客户端的套接字获取的输出流。
定义其 write 方法,像浏览器写出一个响应头,并且包含我们要写出的内容 content。
package tomcat.dome;

import java.io.IOException;
import java.io.OutputStream;

// 实现自己的响应类
public class MyResponse {
// 定义输出流
private OutputStream outputStream;

// 构造函数 传入输出流
public MyResponse(OutputStream outputStream) {
this.outputStream=outputStream;
}

// 创建写出方法
public void write(String content)throws IOException{
// 用来存放要写出数据的容器
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append(“HTTP/1.1 200 OK\r\n”)
.append(“Content-type:text/html\r\n”)
.append(“\r\n”)
.append(“<html><head><title>Hello World</title></head><body>”)
.append(content)
.append(“</body><html>”);

// 转换成字节数组 并进行写出
outputStream.write(stringBuffer.toString().getBytes());
//System.out.println(“sss”);
outputStream.close();
}

}

创建 MyServlet 对象
由于我们自己写一个 Servlet 的时候需要继承 HttpServlet,因此在这里首先定义了一个抽象类——MyServlet 对象。
在其中定义了两个需要子类实现的抽象方法 doGet 和 doSet。
并且创建了一个 service 方法,通过对传入的 request 对象的请求方式进行判断,确定调用的是 doGet 方法或是 doPost 方法。
package tomcat.dome;

// 写一个抽象类作为 servlet 的父类
public abstract class MyServlet {
// 需要子类实现的抽象方法
protected abstract void doGet(MyRequest request,MyResponse response);
protected abstract void doPost(MyRequest request,MyResponse response);

// 父类自己的方法
// 父类的 service 方法对传入的 request 以及 response
// 的方法类型进行判断,由此调用 doGet 或 doPost 方法
public void service(MyRequest request,MyResponse response) throws NoSuchMethodException {
if(request.getMethod().equalsIgnoreCase(“POST”)) {
doPost(request, response);
}else if(request.getMethod().equalsIgnoreCase(“GET”)) {
doGet(request, response);
}else {
throw new NoSuchMethodException(“not support”);
}
}
}

创建业务相关的 Servlet
这里我创建了两个业务相关类:StudentServlet 和 TeacherServlet。
package tomcat.dome;

import java.io.IOException;

// 实现自己业务相关的 Servlet
public class StudentServlet extends MyServlet{

@Override
protected void doGet(MyRequest request, MyResponse response) {
// 利用 response 中的输出流 写出内容
try {
//System.out.println(“!!!!!!!!!!!!!!!!!!”);
response.write(“I am a student.”);
//System.out.println(“9999999999999999”);
}catch(IOException e) {
e.printStackTrace();
}
}

@Override
protected void doPost(MyRequest request, MyResponse response) {
// 利用 response 中的输出流 写出内容
try {
response.write(“I am a student.”);
}catch(IOException e) {
e.printStackTrace();
}
}

}

package tomcat.dome;

import java.io.IOException;

// 实现自己业务相关的 Servlet
public class TeacherServlet extends MyServlet{

@Override
protected void doGet(MyRequest request, MyResponse response) {
// 利用 response 中的输出流 写出内容
try {
response.write(“I am a teacher.”);
}catch(IOException e) {
e.printStackTrace();
}
}

@Override
protected void doPost(MyRequest request, MyResponse response) {
// 利用 response 中的输出流 写出内容
try {
response.write(“I am a teacher.”);
}catch(IOException e) {
e.printStackTrace();
}
}

}

创建映射关系结构 ServletMapping
该结构实现的是请求的 url 与具体的 Servlet 之间的关系映射。
package tomcat.dome;

// 请求 url 与项目中的 servlet 的映射关系
public class ServletMapping {
//servlet 的名字
private String servletName;
// 请求的 url
private String url;
//servlet 类
private String clazz;
public String getServletName() {
return servletName;
}
public void setServletName(String servletName) {
this.servletName = servletName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
public ServletMapping(String servletName, String url, String clazz) {
super();
this.servletName = servletName;
this.url = url;
this.clazz = clazz;
}
}

映射关系配置对象 ServletMappingConfig
配置类中定义了一个列表,里面存储着项目中的映射关系。
package tomcat.dome;

import java.util.ArrayList;
import java.util.List;

// 创建一个存储有请求路径与 servlet 的对应关系的 映射关系配置类
public class ServletMappingConfig {
// 使用一个 list 类型 里面存储的是映射关系类 Mapping
public static List<ServletMapping>servletMappings=new ArrayList<>(16);

// 向其中添加映射关系
static {
servletMappings.add(new ServletMapping(“student”,”/student”, “tomcat.dome.StudentServlet”));
servletMappings.add(new ServletMapping(“teacher”,”/teacher”, “tomcat.dome.TeacherServlet”));
}
}

主角登场 MyTomcat!
在服务端 MyTomcat 中主要做了如下几件事情:
1)初始化请求的映射关系。
2)创建服务端套接字,并绑定某个端口。
3)进入循环,用户接受客户端的链接。
4)通过客户端套接字创建 request 与 response 对象。
5)根据 request 对象的请求方式调用相应的方法。
6)启动 MyTomcat!
package tomcat.dome;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

//Tomcat 服务器类 编写对请求做分发处理的相关逻辑
public class MyTomcat {
// 端口号
private int port=8080;
// 用于存放请求路径与对应的 servlet 类的请求映射关系的 map
// 相应的信息从配置类中获取
private Map<String, String>urlServletMap=new HashMap<>(16);
// 构造方法
public MyTomcat(int port) {
this.port=port;
}

//tomcat 服务器的启动方法
public void start() {
// 初始化请求映射关系
initServletMapping();
// 服务端的套接字
ServerSocket serverSocket=null;
try {
// 创建绑定到某个端口的服务端套接字
serverSocket=new ServerSocket(port);
System.out.println(“MyTomcat begin start…”);
// 循环 用于接收客户端
while(true) {
// 接收到的客户端的套接字
Socket socket=serverSocket.accept();
// 获取客户端的输入输出流
InputStream inputStream=socket.getInputStream();
OutputStream outputStream=socket.getOutputStream();
// 通过输入输出流创建请求与响应对象
MyRequest request=new MyRequest(inputStream);
MyResponse response=new MyResponse(outputStream);

// 根据请求对象的 method 分发请求 调用相应的方法
dispatch(request, response);
// 关闭客户端套接字
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

// 初始化请求映射关系,相关信息从配置类中获取
private void initServletMapping() {
for(ServletMapping servletMapping:ServletMappingConfig.servletMappings) {
urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz());
}
}

// 通过当前的 request 以及 response 对象分发请求
private void dispatch(MyRequest request,MyResponse response) {
// 根据请求的 url 获取对应的 servlet 类的 string
String clazz=urlServletMap.get(request.getUrl());
//System.out.println(“=====”+clazz);
try {
// 通过类的 string 将其转化为对象
Class servletClass=Class.forName(“tomcat.dome.StudentServlet”);
// 实例化一个对象
MyServlet myServlet=(MyServlet)servletClass.newInstance();

// 调用父类方法,根据 request 的 method 对调用方法进行判断
// 完成对 myServlet 中 doGet 与 doPost 方法的调用
myServlet.service(request, response);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}

//main 方法 直接启动 tomcat 服务器
public static void main(String[] args) {
new MyTomcat(8080).start();
}

}

测试结果

正文完
 0