缘起
用了那么久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();
}
}
复制代码