考虑自己实现一个Tomcat,都有哪些关键的要点呢?
第一,提供 Socket 服务
Tomcat 的启动,必然是 Socket 服务,只不过它支持 HTTP 协议而已!
这里其实可以扩展思考下,Tomcat 既然是基于 Socket,那么是基于BIO or NIO or AIO 呢?
第二,进行请求的分发
要知道一个 Tomcat 可以为多个 Web 应用提供服务,那么很显然,Tomcat 可以把 URL 下发到不同的Web应用。
第三,需要把请求和响应封装成`request / response
我们在 Web 应用这一层,可从来没有封装过 request/response 的,我们都是直接使用的,这就是因为 Tomcat 替我们做好了这一步。
创建MyRequest对象
首先创建自定义的请求类,其中定义url与method两个属性,表示请求的url以及请求的方式。
其构造函数需要传入一个输入流,该输入流通过客户端的套接字对象得到。
package com.king.utils;
//实现自己的请求类
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];
System.out.println("MyRequests header:"+httpHead);
if(httpHead!=null){
url=httpHead.split("\\s")[1];
method=httpHead.split("\\s")[0];
System.out.println("MyRequests = [ "+this+" ]"+method+":"+url);
}
}
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 com.king.utils;
//实现自己的响应类
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 com.king.utils;
//写一个抽象类作为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
这里我创建了两个业务相关类
创建映射关系结构ServletMapping
该结构实现的是请求的url与具体的Servlet之间的关系映射。
package com.king.utils;
//请求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 com.king.utils;
//创建一个存储有请求路径与servlet的对应关系的 映射关系配置类
public class ServletMappingConfig {
//使用一个list类型 里面存储的是映射关系类Mapping
public static List<ServletMapping>servletMappings=new ArrayList<>(16);
//向其中添加映射关系
static {
//TODO使用解析web.xml的配置文件的方式替换硬编码。
servletMappings.add(new ServletMapping("first","/first", "com.king.servlet.FirstServlet"));
servletMappings.add(new ServletMapping("second","/second", "com.king.servlet.SecondServlet"));
servletMappings.add(new ServletMapping("test","/test", "com.king.servlet.TestServlet"));//这个不存在的
}
}
自定义tomcat服务端MyTomcat
在服务端MyTomcat中主要做了如下几件事情:
1)初始化请求的映射关系。
2)创建服务端套接字,并绑定某个端口。
3)进入循环,用户接受客户端的链接。
4)通过客户端套接字创建request与response对象。
5)根据request对象的请求方式调用相应的方法。
6)启动MyTomcat!
package com.king;
//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();
//TODO 使用NIO替换BIO
//服务端的套接字
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) throws IOException {
//根据请求的url获取对应的servlet类的string
String clazz=urlServletMap.get(request.getUrl());
System.out.println("====="+clazz);
if(clazz==null){
response.write("404");
return;
}
try {
//通过类的string将其转化为对象
Class servletClass=Class.forName(clazz);
//实例化一个对象
MyServlet myServlet=(MyServlet)servletClass.newInstance();
//调用父类方法,根据request的method对调用方法进行判断
//完成对myServlet中doGet与doPost方法的调用
myServlet.service(request, response);
} catch (ClassNotFoundException e) {
e.printStackTrace();
response.write("501");
} 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();
}
}
浏览器输入http://127.0.0.1:8080/first
即可显示返回
FirstServlet servlet get>>>
当前共有 0 条评论