【学习条记】手写 Tomcat 二

[复制链接]
发表于 2026-1-24 22:42:41 | 显示全部楼层 |阅读模式
目次
一、HTTP协议哀求格式
二、相应静态资源
1. 剖析哀求信息
创建剖析哀求类 HttpRequest
2. 创建静态资源目次 webs
3. 封装相应信息
创建静态资源处置惩罚器 StaticResourceHandler
创建相应类 HttpResponse
调用相应类相应数据
测试
静态资源的路径分析
三、作业
1. 绘制 哀求剖析类 HttpRequest 和相应类 HttpResponse 的封装流程图
2. 优化客户端的毗连


一、HTTP协议哀求格式



二、相应静态资源

在昨天的根本上,再进一步优化,可以大概相应HTML文件,图片等静态资源
在相应静态资源之前,我们要先确定客户端哀求的是哪个静态资源,是HTML页面照旧图片呢?
我们先打印一下客户端的哀求信息



可以看到,第一行也就是 哀求行 ,它有哀求方法,哀求URL和哀求协议
哀求URL就是须要哀求的资源


1. 剖析哀求信息

那怎样拿到 哀求的URL呢?然后根据哀求URL相应对应的资源
我们看一下 HTTP协议的哀求格式,不难发现,都是以回车符和换行符末了
以 回车符和换行符举行分割,然后再根据 空格 分割就可以拿到哀求URL了

创建剖析哀求类 HttpRequest



  1. package com.shao.net;
  2. import java.util.HashMap;
  3. public class HttpRequest {
  4.     /**
  5.      * 请求信息
  6.      */
  7.     private String msg;
  8.     /**
  9.      * 请求行
  10.      */
  11.     private String requestLine;
  12.     /**
  13.      * 请求行的请求方法
  14.      */
  15.     private String requestMethod;
  16.     /**
  17.      * 请求行的请求URI,请求路径
  18.      */
  19.     private String requestURI;
  20.     /**
  21.      * 请求行的请求模块,例如:/index.html?a=1 请求模块为:/index.html
  22.      */
  23.     private String requestModule;
  24.     /**
  25.      * 请求行的请求协议
  26.      */
  27.     private String requestProtocol;
  28.     /**
  29.      * 存储请求头参数
  30.      */
  31.     private HashMap<String, String> requestHeaderParams = new HashMap<>();
  32.     /**
  33.      * 存储请求体参数
  34.      */
  35.     private HashMap<String, String> requestBodyParams = new HashMap<>();
  36.     /**
  37.      * 构造函数
  38.      */
  39.     public HttpRequest(String msg) {
  40.         this.msg = msg;
  41.         // 根据HTTP协议格式 分割请求信息
  42.         String[] requestArr = msg.split("\r\n");
  43.         // 把数组中的第一个元素赋值给请求行
  44.         requestLine = requestArr[0];
  45.         // 1. 解析请求行
  46.         parseRequestLine();
  47.         // 2. 解析请求头
  48.         parseRequestHeader(requestArr);
  49.         // 3. 解析请求体
  50.         parseRequestBody();
  51.     }
  52.     // 1. 解析请求行
  53.     private void parseRequestLine() {
  54.         // 把请求行按空格分割
  55.         String[] requestParts = requestLine.split(" ");
  56.         // 请求方法
  57.         requestMethod = requestParts[0];
  58.         // 请求资源路径
  59.         requestURI = requestParts[1];
  60.         // 请求协议
  61.         requestProtocol = requestParts[2];
  62.         // 如果请求方法是GET,则根据 ? 号分割,获取请求模块,例如:/index.html?a=1 请求模块为:/index.html
  63.         String[] split = requestURI.split("\\?");
  64.         requestModule = split[0];
  65.         System.out.println("请求方法:" + requestMethod);
  66.         System.out.println("请求uri:" + requestURI);
  67.         System.out.println("请求协议:" + requestProtocol);
  68.         System.out.println("请求模块:" + requestModule);
  69.     }
  70.     // 2. 解析请求头
  71.     private void parseRequestHeader(String[] requestArr) {
  72.         // 检查请求数组,判断是否为空,判断有没有请求头
  73.         if (requestArr == null || requestArr.length <= 1) {
  74.             return;
  75.         }
  76.         // 遍历请求数组,从第二个元素开始,因为第一个元素是请求行
  77.         for (int i = 1; i < requestArr.length; i++) {
  78.             // 判断是否为空,如果为空,表示请求头结束,因为 HTTP 协议中,请求头和请求体之间有2个回车符 换行符
  79.             String headerLine = requestArr[i];
  80.             if (headerLine.length() == 0) {
  81.                 break;
  82.             }
  83.             // 把请求头按 :  分割,获取请求头参数
  84.             // 注意:这里需要使用 ": ",而不是 ":"
  85.             String[] headerParts = headerLine.split(": ");
  86.             // 判断请求头的格式是否正确
  87.             if (headerParts.length >= 2) {
  88.                 requestHeaderParams.put(headerParts[0], headerParts[1]);
  89.             }
  90.         }
  91.         System.out.println("请求头参数:" + requestHeaderParams);
  92.     }
  93.     // 3. 解析请求体
  94.     private void parseRequestBody() {
  95.         // POST 方法的请求参数是在请求体,GET 方法的请求参数在请求行
  96.         if (this.requestMethod.equalsIgnoreCase("POST")) {
  97.             // 分割请求信息
  98.             String[] split = msg.split("\r\n\r\n");
  99.             // 如果分割后的长度 >= 2,表示有请求体
  100.             if (split.length >= 2) {
  101.                 // 分割请求体
  102.                 splitRequestBody(split[1]);
  103.             }
  104.         } else if (this.requestMethod.equalsIgnoreCase("GET")) {
  105.             // 把请求行按空格分割,获取请求参数
  106.             String[] requestLineParts = this.requestLine.split(" ");
  107.             String[] split = requestLineParts[1].split("\\?");
  108.             if (split.length >= 2) {
  109.                 // 分割请求体
  110.                 splitRequestBody(split[1]);
  111.             }
  112.         }
  113.         System.out.println("请求体参数:" + requestBodyParams);
  114.     }
  115.     private void splitRequestBody(String requestBody) {
  116.         // 分割请求参数,例如:a=1&b=2&c=3
  117.         String[] requestBodyParts = requestBody.split("&");
  118.         // 遍历请求体
  119.         for (int i = 0; i < requestBodyParts.length; i++) {
  120.             String part = requestBodyParts[i];
  121.             // 把请求参数按 = 分割,获取键值对,最多分割2部分,如果有多个 = ,只保留第一个 = 之前和之后的部分
  122.             String[] keyValue = part.split("=", 2);
  123.             if (keyValue.length == 2) {
  124.                 requestBodyParams.put(keyValue[0], keyValue[1]);
  125.             } else {
  126.                 System.out.println("警告:非法格式的请求体:" + part);
  127.             }
  128.         }
  129.     }
  130.     /**
  131.      * 获取
  132.      * @return msg
  133.      */
  134.     public String getMsg() {
  135.         return msg;
  136.     }
  137.     /**
  138.      * 设置
  139.      * @param msg
  140.      */
  141.     public void setMsg(String msg) {
  142.         this.msg = msg;
  143.     }
  144.     /**
  145.      * 获取
  146.      * @return requestLine
  147.      */
  148.     public String getRequestLine() {
  149.         return requestLine;
  150.     }
  151.     /**
  152.      * 设置
  153.      * @param requestLine
  154.      */
  155.     public void setRequestLine(String requestLine) {
  156.         this.requestLine = requestLine;
  157.     }
  158.     /**
  159.      * 获取
  160.      * @return requestMethod
  161.      */
  162.     public String getRequestMethod() {
  163.         return requestMethod;
  164.     }
  165.     /**
  166.      * 设置
  167.      * @param requestMethod
  168.      */
  169.     public void setRequestMethod(String requestMethod) {
  170.         this.requestMethod = requestMethod;
  171.     }
  172.     /**
  173.      * 获取
  174.      * @return requestURI
  175.      */
  176.     public String getRequestURI() {
  177.         return requestURI;
  178.     }
  179.     /**
  180.      * 设置
  181.      * @param requestURI
  182.      */
  183.     public void setRequestURI(String requestURI) {
  184.         this.requestURI = requestURI;
  185.     }
  186.     /**
  187.      * 获取
  188.      * @return requestModule
  189.      */
  190.     public String getRequestModule() {
  191.         return requestModule;
  192.     }
  193.     /**
  194.      * 设置
  195.      * @param requestModule
  196.      */
  197.     public void setRequestModule(String requestModule) {
  198.         this.requestModule = requestModule;
  199.     }
  200.     /**
  201.      * 获取
  202.      * @return requestProtocol
  203.      */
  204.     public String getRequestProtocol() {
  205.         return requestProtocol;
  206.     }
  207.     /**
  208.      * 设置
  209.      * @param requestProtocol
  210.      */
  211.     public void setRequestProtocol(String requestProtocol) {
  212.         this.requestProtocol = requestProtocol;
  213.     }
  214.     /**
  215.      * 获取
  216.      * @return requestHeaderParams
  217.      */
  218.     public HashMap<String, String> getRequestHeaderParams() {
  219.         return requestHeaderParams;
  220.     }
  221.     /**
  222.      * 设置
  223.      * @param requestHeaderParams
  224.      */
  225.     public void setRequestHeaderParams(HashMap<String, String> requestHeaderParams) {
  226.         this.requestHeaderParams = requestHeaderParams;
  227.     }
  228.     /**
  229.      * 获取
  230.      * @return requestBodyParams
  231.      */
  232.     public HashMap<String, String> getRequestBodyParams() {
  233.         return requestBodyParams;
  234.     }
  235.     /**
  236.      * 设置
  237.      * @param requestBodyParams
  238.      */
  239.     public void setRequestBodyParams(HashMap<String, String> requestBodyParams) {
  240.         this.requestBodyParams = requestBodyParams;
  241.     }
  242.     public String toString() {
  243.         return "HttpRequest{msg = " + msg + ", requestLine = " + requestLine + ", requestMethod = " + requestMethod + ", requestURI = " + requestURI + ", requestModule = " + requestModule + ", requestProtocol = " + requestProtocol + ", requestHeaderParams = " + requestHeaderParams + ", requestBodyParams = " + requestBodyParams + "}";
  244.     }
  245. }
复制代码

调用 HttpRequest 剖析哀求信息

游览器发送哀求


打印剖析后的哀求信息,可以看到哀求的静态资源是 /index.html


拿到了哀求uri,我们就可以根据 哀求uri 相应静态资源了
那这个静态资源放在哪儿呢?
2. 创建静态资源目次 webs

创建一个 HTML 文件,内容先写英文,写中文须要在相应头加上 UTF-8 字符编码


那如今 index.html 有了,怎么相应给客户端呢?
读取 index.html 文件,将读取到的内容发送到游览器,游览器会主动渲染页面
3. 封装相应信息

把相应信息的代码拿出来,单独放到一个类里,这个类就只须要相应数据
如答应以低沉代码的耦合度


在创建相应类之前有两个标题
1. 怎么确定哀求的是静态资源?动态资源一样平常须要操纵数据库
2. 相应的媒体范例怎么写?
如果相应的是图片,那么就须要修改为 Content-Type:image/jpeg 或 image/png 等
以是,就须要动态的判断相应的静态资源是什么媒体范例。
创建静态资源处置惩罚器 StaticResourceHandler



  1. package com.shao.net;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.IOException;
  5. import java.nio.file.Files;
  6. import java.nio.file.Paths;
  7. public class StaticResourceHandler {
  8.     // 常见静态资源扩展名
  9.     private static final String[] staticExtensions = new String[]{".html", ".css", ".js", ".jpg", ".png", ".gif", ".ico", ".svg", ".pdf", ".txt"};
  10.     /**
  11.      * 判断是否静态资源
  12.      * 如果是静态资源则返回 true ,否则返回 false
  13.      */
  14.     public static boolean isLikelyStaticResource(String fileName) {
  15.         // 检查文件的扩展名是否存在于静态资源扩展名数组中
  16.         for (String ext : staticExtensions) {
  17.             if (fileName.endsWith(ext)) {
  18.                 return true;
  19.             }
  20.         }
  21.         return false;
  22.     }
  23.     /**
  24.      * 根据文件路径获取文件内容
  25.      */
  26.     public static byte[] getFileContents(String filePath) {
  27.         // 1. 获取文件对象
  28.         File file = new File(filePath);
  29.         // 2. 判断文件是否存在
  30.         if (!file.exists()) {
  31.             return null;
  32.         }
  33.         // 3. 获取文件内容
  34.         // 定义一个字节数组,数组大小根据文件大小确定
  35.         byte[] fileContents = new byte[0];
  36.         try {
  37.             FileInputStream fis = new FileInputStream(file);
  38.             // fis.available() 是读取的文件的字节数
  39.             fileContents = new byte[fis.available()];
  40.             // 读取文件存放到 fileContents 数组中
  41.             fis.read(fileContents);
  42.             // 关闭文件输入流
  43.             fis.close();
  44.         } catch (Exception e) {
  45.             e.printStackTrace();
  46.         }
  47.         return fileContents;
  48.     }
  49.     /**
  50.      * 获取文件的媒体类型
  51.      */
  52.     public static String getFileMimeType(String filePath) {
  53.         // 1 获取文件对象
  54.         File file = new File(filePath);
  55.         // 2 判断文件是否存在
  56.         if (!file.exists()) {
  57.             return "text/html";
  58.         }
  59.         // 3 获取文件的媒体类型
  60.         String fileType = null;
  61.         try {
  62.             fileType = Files.probeContentType(Paths.get(filePath));
  63.         } catch (IOException e) {
  64.             e.printStackTrace();
  65.         }
  66.         return fileType;
  67.     }
  68. }
复制代码

创建相应类 HttpResponse



  1. package com.shao.net;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.io.OutputStream;
  5. public class HttpResponse {
  6.     /**
  7.      * 输出流
  8.      */
  9.     private OutputStream os;
  10.     /**
  11.      * 解析请求信息的对象
  12.      */
  13.     private HttpRequest httpRequest;
  14.     public HttpResponse(OutputStream os, HttpRequest httpRequest) {
  15.         this.os = os;
  16.         this.httpRequest = httpRequest;
  17.     }
  18.     public void response(String filePath) {
  19.         //判断请求的是否为静态文件
  20.         if (StaticResourceHandler.isLikelyStaticResource(httpRequest.getRequestModule())) {
  21.             // 获取静态资源一般是 GET 请求方法
  22.             if (httpRequest.getRequestMethod().equals("GET")) {
  23.                 // 响应静态资源
  24.                 responseStaticResource(filePath);
  25.             }
  26.         } else {
  27.             // 处理动态请求
  28.             System.out.println("请求动态资源");
  29.         }
  30.     }
  31.     /**
  32.      * 响应静态资源
  33.      */
  34.     private void responseStaticResource(String filePath) {
  35.         // 读取文件
  36.         byte[] fileContents = StaticResourceHandler.getFileContents(filePath);
  37.         // 判断文件是否存在,不存在则返回 404 的页面
  38.         if (fileContents == null) {
  39.             try {
  40.                 FileInputStream fis = new FileInputStream("webs/pages/not_Found404.html");
  41.                 fileContents = new byte[fis.available()];
  42.                 fis.read(fileContents);
  43.                 fis.close();
  44.             } catch (Exception e) {
  45.                 e.printStackTrace();
  46.             }
  47.         }
  48.         // 响应协议
  49.         String protocol = httpRequest.getRequestProtocol();
  50.         // 文件媒体类型
  51.         String fileMimeType = StaticResourceHandler.getFileMimeType(filePath);
  52.         try {
  53.             os.write((protocol + " 200 OK\r\n").getBytes());
  54.             os.write(("Content-Type: " + fileMimeType + "\r\n").getBytes());
  55.             os.write(("Content-Length: " + fileContents.length + "\r\n").getBytes());
  56.             os.write("\r\n".getBytes());
  57.             os.write(fileContents);
  58.             os.flush();
  59.             System.out.println("响应成功");
  60.             os.close();
  61.         } catch (IOException e) {
  62.             e.printStackTrace();
  63.         }
  64.     }
  65. }
复制代码

调用相应类相应数据



测试



静态资源的路径分析



可以直接复制文件的相对路径 





三、作业

1. 绘制 哀求剖析类 HttpRequest 和相应类 HttpResponse 的封装流程图

2. 优化客户端的毗连

如今只有主线程处置惩罚客户端毗连,如果同一时间有多个客户端发起毗连,同一时间只能处置惩罚一个客户端的毗连,如许服从不高,怎么处置惩罚?
办理方案是来一个客户端毗连就创建一条线程行止置惩罚



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表