源码放在了文章末尾
Tomcat是一个开源的Servlet容器,它实现了Java Servlet、JavaServer Pages (JSP)、WebSocket等Java EE规范,用于在Web服务器上运行Java Web应用程序。
说的简单点,Tomcat能处理网络传输来的请求。
也就是说,Tomcat要帮我们完成客户端和服务器之间的连接、传输。传输的时候是用输入输出流来传输的。
客户端和服务器的通信,说到底就是两个数据的传输,客户端发送inputStream给服务器,服务器回复outputStream给客户端。
http请求也就是 web浏览器发送给web服务器(Tomcat)之间的传输数据协议。也就是商量好一个格式去传输,这样服务器收到了之后,就能对其进行解析了,就知道了浏览器想表达的意思,再对其进行反馈。
http请求协议部分数据
GET /user HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
第一部分:请求行:请求类型,资源路径以及http版本(上述第一行)
第二部分:请求头:紧接在请求行之后,用于说明服务器需要使用的附加信息(第二到第八行)
第三部分:空行(请求头和主体之间必须有换行)
第四部分:主体数据,可以添加任意数据
HTTP响应是Web服务器向客户端(通常是浏览器)返回的数据。当客户端发送HTTP请求后,服务器会根据请求的内容和要求生成一个HTTP响应,将其发送回客户端。在仿写Tomcat时,了解HTTP响应的结构和内容是很重要的。
HTTP 响应是服务器向客户端发送的数据,用于回应客户端的请求。HTTP 响应需要满足一定的格式和要求,以下是一个标准的 HTTP 响应的格式
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 37
简单的HTTP响应示例
Hello, World!
HTTP/1.1 200 OK
:状态行,表示HTTP协议版本为1.1,状态码为200,状态短语为OK。这表示请求成功。
Content-Type: text/html
:响应头部,指定响应内容的类型为HTML。
Content-Length: 37
:响应头部,指定响应内容的长度,以字节为单位。
空行:用于分隔响应头部和响应体。
响应体:实际的响应内容。在本例中,它是一个简单的HTML页面,显示了"Hello, World!"。
在Web服务中,静态请求和动态请求是两种不同类型的HTTP请求,用于获取和呈现网页内容。它们有不同的特点和用途:
总之,静态请求和动态请求在Web服务中都起着重要作用,静态请求用于提供固定不变的资源,而动态请求用于生成个性化、实时更新的内容。
启动Tomcat
访问 首页
url输入错误,404页面测试
静态请求测试:访问html页面
静态请求测试:css
动态请求测试:登录
客户端(浏览器)发送一个请求,Tomcat一直在监听,当受到请求后,开始解析这个请求信息,解析完毕开始处理请求,处理完毕就封装响应信息,再返回给前端。
详细的流程请看下面讲解
具体就是做下面四个步骤
这四步骤也就是tomcat的全部了,但是具体的每个步骤的细分还有很多
请求信息类,对客户端收到的输入流数据进行解析与封装,得到request对象
将输入流转换成String,开始解析
/**
* 将输入流转换成String,开始解析
*/
public HttpServletRequest(InputStream iis) {
//一次性读完所有请求信息
StringBuilder sb = new StringBuilder();
int length = -1;
byte[] bs = new byte[100*1024];
try {
length = iis.read(bs);//读取socket输入流数据,将其放到byte数组里面
} catch (IOException e) {
e.printStackTrace();
System.out.println("读取客户请求异常");
}
//将bs中的字节数据转为char
for(int i = 0;i
解析协议
/**
* 解析协议
*/
private void parseProtocol() {
String[] ss = content.split(" ");
//解析 请求方法类型,存到method
this.method = ss[0];
//解析 请求地址,存到requestURI
this.requestURI = ss[1];
//解析 请求参数,存到parameter的map中
parseParameter();
//解析 请求头,存到headers中
parseHeader();
//解析 请求cookie:从headers中取cookie
parseCookie();
//解析 sessionId:从cookie中取出jsessionid
jsessionid = parseJSessionId();
}
各种解析方法
private String parseJSessionId() {
if(cookies!=null&&cookies.size()>0) {
for(Cookie c:cookies) {
if("JSESSIONID".equals(c.getName())) {
return c.getValue();
}
}
}
return null;
}
/**
* headers中取出cookie,然后在解析出cookie对象存在cookies中
* 取出协议中的 Cookie:xxxx ,如果有则说明已经生成过Cookie 没有则表明是第一次请求,要生成Cookie编号
*/
private void parseCookie() {
if(headers==null&&headers.size()<=0){
return;
}
//从headers中取出键为cookie的
String cookieValue = headers.get("Cookie");
if(cookieValue == null || cookieValue.length()<=0) {
return;
}
String[] cvs = cookieValue.split(": ");
if(cvs.length > 0) {
for(String cv:cvs) {
String[] str = cv.split("=");
if(str.length > 0) {
String key = str[0];
String value = str[1];
Cookie c = new Cookie(key,value);
cookies.add(c);
}
}
}
}
private void parseHeader() {
//请求头
String[] parts = this.content.split("\r\n\r\n");
//GET /请求地址 HTTP/1.1
String[] headerss = parts[0].split("\r\n");
for(int i = 1;i=1){
String[] pairs = this.requestURI.substring(index+1).split("&");
for(String p:pairs){
String[] po = p.split("=");
parameter.put(po[0], po[1]);
}
}
if(this.method.equals("POST")){
String[] parts = this.content.split("\r\n\r\n");
String entity = parts[1];
String[] pairs = entity.split("&");
for(String p:pairs){
String[] po = p.split("=");
parameter.put(po[0], po[1]);
}
}
}
响应类:根据传过来的请求对象拿到 URL,找到请求的资源文件,设置对应的响应类型,将文件写入到响应流中返回
如果是静态请求,就会调用到相应类,因为静态请求就是要获取某个HTML、CSS、JavaScript、图像等文件,所以我们只需要从请求url中解析出文件的名称,再找到这个文件,再按照http响应的格式的写入到输出流中,返回给前端就行了。
//3、发送文件响应,不同的文件返回不同类型
if(file.getName().endsWith(".jpg")){
send(file,"application/x-jpg",code);
}else if(file.getName().endsWith(".jpe")||file.getName().endsWith(".jpeg")){
send(file,"image/jpeg",code);
}else if(file.getName().endsWith(".gif")){
send(file,"image/gif",code);
}else if(file.getName().endsWith(".css")){
send(file,"text/css",code);
}else if(file.getName().endsWith(".js")){
send(file,"application/x-javascript",code);
}else if(file.getName().endsWith(".swf")){
send(file,"application/x-shockwave-flash",code);
}else{
send(file,"text/html",code);
}
send方法先调用genProtocol方法,先拼接好响应格式
/**
* 拼接响应协议
*/
private String genProtocol(long length, String contentType, int code) {
String result = "HTTP/1.1 "+code+" OK\r\n";
result+="Server: myTomcat\r\n";
result+="Content-Type: "+contentType+";charset=utf-8\r\n";
result+="Content-Length: "+length+"\r\n";
result+="Date: "+new Date()+"\r\n";
result+="\r\n";
return result;
}
send方法再调用readFile方法把文件读出来,返回字节数组
/**
* 读取文件
*/
private byte[] readFile(File file) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] bs = new byte[1024];
int length;
while((length = fis.read(bs,0,bs.length))!=-1){
baos.write(bs, 0, length);
baos.flush();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return baos.toByteArray();
}
最后send方法就返回给前端流数据了
/**
* 返回给前端响应流
*/
private void send(File file, String contentType, int code) {
try {
String responseHeader = genProtocol(file.length(),contentType,code);
byte[] bs = readFile(file);
this.oos.write(responseHeader.getBytes());
this.oos.flush();//往前端传过去
this.oos.write(bs);
this.oos.flush();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
this.oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
静态处理就是前端需要一些静态的资源,例如HTML、CSS、图片等,后端直接找到文件,按照格式返回给前端就行了
动态处理 就有对应得到业务了,要调用对应的servlet
区分动态还是静态,我这里是这样区分的:URL中有.action的就是动态,其他就是静态,下面代码就是在线程任务处理类里面的代码。
处理接口主要就是一个处理类的接口,动态处理类和静态处理类实现了它
/**
* 处理器
* 处理请求的接口
*
* @author 康有为
* @date 2023/08/17
*/
public interface Processor {
/** 处理请求,给出响应
* @param request 请求
* @param response 响应
*/
void process(HttpServletRequest request, HttpServletResponse response);
}
静态处理类就很简单,直接调用相应类的sendRedirect方法,返回给前端就行了。
/**
* 静态处理器
* 实现处理接口
*
* @author 康有为
* @date 2023/08/17
*/
public class StaticProcessor implements Processor {
@Override
public void process(HttpServletRequest request, HttpServletResponse response) {
//调用响应类的 sendRedirect方法
response.sendRedirect();
}
}
动态处理类
因为动态处理的URL中肯定是有“.action”结尾的(我们自定义的),所以我们要解析URL,拿到.action 前面的那些东西。
例如:localhost:8888/User.action 这个请求,我们拿到User之后,再后面拼接一个“Servlet”,再利用反射,在对应的存放servlet实例文件目录中找到UserServlet,再调用其对应的servlet的方法就行了。
这里就是对前端的动态请求进行处理和反馈了,具体的servlet实例就根据不同的业务来处理就行。
注意:servlet实例必须要放到指定的目录下“servlet”,因为我们处理动态请求的时候是用反射来扫描了“servlet”目录,放在其他目录下就找不到了
例如下图,是登录的servlet,那就返回给前端登录成功,并附上session即可。
参考文章:【Tomcat】——纯手写实现一个简单的Tomcat_手写tomcat实现部署功能_土豆是我的最爱的博客-CSDN博客
具体的详细代码,请看源码 康有为/手写Tomcat - 码云 - 开源中国 (gitee.com)
喜欢的话,点赞支持一波