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

缘起

用了那么久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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值