手写一个民用Tomcat (04)

我们继续来 写 Tomcat  这次我们做优化,先看一下一个标准的http 协议

GET /servlet/com.yixin.HelloWorldServlet HTTP/1.1
Host: localhost:8080
Connection: keep-alive
sec-ch-ua: "Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
sec-ch-ua-platform: "Windows"
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8

我们这一版就要是把所需要的数据 取出来,放到map 集合里边,先看第一个 我们能获取 请求方式GET, 请求路径 以及 请求协议 HTTP/1.1  然后 空一行, 获取到 请求头。

如果根据 这个报文结构,让各位写一个  获取协议 应该不难吧, 什么 split(),indexof, 什么subString  整吧,但是 tomcat 源码中就不会这么干了。

来 我们看下 ,

HttpRequestLine 类

public class HttpRequestLine {public static final int INITIAL_METHOD_SIZE = 8;public static final int INITIAL_URI_SIZE = 128;public static final int INITIAL_PROTOCOL_SIZE = 8;public static final int MAX_METHOD_SIZE = 32;public static final int MAX_URI_SIZE = 2048;public static final int MAX_PROTOCOL_SIZE = 32;//下面的属性对应于Http Request规范,即头行格式method uri protocol
//如:GET /hello.txt HTTP/1.1
//char[] 存储每段的字符串,对应的int值存储的是每段的结束位置public char[] method;public int methodEnd;public char[] uri;public int uriEnd;public char[] protocol;public int protocolEnd;public HttpRequestLine() {this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0,new char[INITIAL_PROTOCOL_SIZE], 0);}public HttpRequestLine(char[] method, int methodEnd,char[] uri, int uriEnd,char[] protocol, int protocolEnd) {this.method = method;this.methodEnd = methodEnd;this.uri = uri;this.uriEnd = uriEnd;this.protocol = protocol;this.protocolEnd = protocolEnd;}public void recycle() {methodEnd = 0;uriEnd = 0;protocolEnd = 0;}public int indexOf(char[] buf) {return indexOf(buf, buf.length);}//这是主要的方法/***   这个方法遵循的 从你查到的第一个字符串开始找*   举个例子 在helloworld 里边找or*   以第一个字符o在helloworld 开始遍历 找到符合的就返回一个pos位置*   然后以pos 为起点  开始循环依次对比 看是否后边的满足 如果不满足 返回 pos++*   继续开始这样找下去 一直遍历完所有helloworld*/public int indexOf(char[] buf, int end) {char firstChar = buf[0];int pos = 0; //pos是查找字符串buf在uri[]中的开始位置while (pos < uriEnd) {pos = indexOfChar(firstChar, pos); //首字符定位开始位置if (pos == -1) {return -1;}//uriEnd - pos 表示url的最后剩余是否小与查找字符串的长度//如果后边都没有了,依旧没有查到 难就查不到了。if ((uriEnd - pos) < end) {return -1;}for (int i = 0; i < end; i++) { //从开始位置起逐个字符比对if (uri[i + pos] != buf[i]) {break;}if (i == (end - 1)) { //每个字符都相等,则匹配上了,返回开始位置return pos;}}pos++;}return -1;}public int indexOf(String str) {return indexOf(str.toCharArray(), str.length());}//在uri[]中查找字符c的出现位置public int indexOfChar(char c, int start) {for (int i = start; i < uriEnd; i++) {if (uri[i] == c) {return i;}}return -1;}}

这个比较简单,就是查找字符串位置 

接下来 比较复杂了。

SocketInputStream 类
public class SocketInputStream extends InputStream {private static final byte CR = (byte) '\r';private static final byte LF = (byte) '\n';// 换行private static final byte SP = (byte) ' ';private static final byte HT = (byte) '\t';private static final byte COLON = (byte) ':';private static final int LC_OFFSET = 'A' - 'a';protected byte buf[];protected int count;protected int pos;protected InputStream is;public SocketInputStream(InputStream is, int bufferSize) {this.is = is;buf = new byte[bufferSize];}//从输入流中解析出request linepublic void readRequestLine(HttpRequestLine requestLine)throws IOException {int chr = 0;
//跳过空行do {try {chr = read();} catch (IOException e) {}} while ((chr == CR) || (chr == LF));
//第一个非空位置pos--;int maxRead = requestLine.method.length;int readStart = pos;int readCount = 0;boolean space = false;
//解析第一段method,以空格结束 解析出 GETwhile (!space) {if (pos >= count) {int val = read();if (val == -1) {throw new IOException("requestStream.readline.error");}pos = 0;readStart = 0;}if (buf[pos] == SP) {space = true;}requestLine.method[readCount] = (char) buf[pos];readCount++;pos++;}requestLine.methodEnd = readCount - 1; //method段的结束位置maxRead = requestLine.uri.length;readStart = pos;readCount = 0;space = false;boolean eol = false;
//解析第二段uri,以空格结束 解析出/servlet/com.yixin.HelloWorldServletwhile (!space) {if (pos >= count) {int val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if (buf[pos] == SP) {space = true;}requestLine.uri[readCount] = (char) buf[pos];readCount++;pos++;}requestLine.uriEnd = readCount - 1; //uri结束位置maxRead = requestLine.protocol.length;readStart = pos;readCount = 0;
//解析第三段protocol,以eol结尾 解析出 HTTP/1.1while (!eol) {if (pos >= count) {int val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if (buf[pos] == CR) {
// Skip CR.} else if (buf[pos] == LF) {eol = true;} else {requestLine.protocol[readCount] = (char) buf[pos];readCount++;}pos++;}requestLine.protocolEnd = readCount;}public void readHeader(HttpHeader header)throws IOException {int chr = read();if ((chr == CR) || (chr == LF)) { // Skipping CRif (chr == CR)read(); // Skipping LFheader.nameEnd = 0;header.valueEnd = 0;return;} else {pos--;}
// 正在读取 header nameint maxRead = header.name.length;int readStart = pos;int readCount = 0;boolean colon = false;while (!colon) {
// 我们处于内部缓冲区的末尾if (pos >= count) {int val = read();if (val == -1) {throw new IOException("requestStream.readline.error");}pos = 0;readStart = 0;}if (buf[pos] == COLON) {colon = true;}char val = (char) buf[pos];if ((val >= 'A') && (val <= 'Z')) {val = (char) (val - LC_OFFSET);}header.name[readCount] = val;readCount++;pos++;}header.nameEnd = readCount - 1;
// 读取 header 值(可以跨越多行)maxRead = header.value.length;readStart = pos;readCount = 0;int crPos = -2;boolean eol = false;boolean validLine = true;while (validLine) {boolean space = true;
// 跳过空格
// 注意:仅删除前面的空格,后面的不删。while (space) {
// 我们已经到了内部缓冲区的尽头if (pos >= count) {
// 将内部缓冲区的一部分(或全部)复制到行缓冲区int val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if ((buf[pos] == SP) || (buf[pos] == HT)) {pos++;} else {space = false;}}while (!eol) {
// 我们已经到了内部缓冲区的尽头if (pos >= count) {
// 将内部缓冲区的一部分(或全部)复制到行缓冲区int val = read();if (val == -1)throw new IOException("requestStream.readline.error");pos = 0;readStart = 0;}if (buf[pos] == CR) {} else if (buf[pos] == LF) {eol = true;} else {
// FIXME:检查二进制转换是否正常int ch = buf[pos] & 0xff;header.value[readCount] = (char) ch;readCount++;}pos++;}int nextChr = read();//Microsoft Edge 因为有的vlaue 值中有空格的情况if ((nextChr != SP) && (nextChr != HT)) {pos--;validLine = false;} else {eol = false;header.value[readCount] = ' ';readCount++;}}header.valueEnd = readCount;}@Overridepublic int read() throws IOException {if (pos >= count) {fill();if (pos >= count) {return -1;}}return buf[pos++] & 0xff;}public int available() throws IOException {return (count - pos) + is.available();}public void close() throws IOException {if (is == null) {return;}is.close();is = null;buf = null;}protected void fill() throws IOException {pos = 0;count = 0;int nRead = is.read(buf, 0, buf.length);if (nRead > 0) {count = nRead;}System.out.println(new String(buf));}
}

总归就是 获取 http协议中的数据 ,readRequestLine 这个方法, 别看复杂就是获取 第一行数据,GET 请求路径,等 

接下来就是获取 请求头数据放入到map 集合:

HttpHeader
public class HttpHeader {public static final int INITIAL_NAME_SIZE = 64;public static final int INITIAL_VALUE_SIZE = 512;public static final int MAX_NAME_SIZE = 128;public static final int MAX_VALUE_SIZE = 1024;public char[] name;public int nameEnd;public char[] value;public int valueEnd;protected int hashCode = 0;public HttpHeader() {this(new char[INITIAL_NAME_SIZE], 0, new char[INITIAL_VALUE_SIZE], 0);}public HttpHeader(char[] name, int nameEnd, char[] value, int valueEnd) {this.name = name;this.nameEnd = nameEnd;this.value = value;this.valueEnd = valueEnd;}public HttpHeader(String name, String value) {this.name = name.toLowerCase().toCharArray();this.nameEnd = name.length();this.value = value.toCharArray();this.valueEnd = value.length();}public void recycle() {nameEnd = 0;valueEnd = 0;hashCode = 0;}
}

这是一个请求头的类

JxdRequest 这个类 需要 只显示 变更的部分:
private InputStream input;
private SocketInputStream sis;
private String uri;
InetAddress address;
int port;
protected HashMap<String, String> headers = new HashMap<>();
protected Map<String, String> parameters = new ConcurrentHashMap<>();
HttpRequestLine requestLine = new HttpRequestLine();public void parse(Socket socket) {try {input = socket.getInputStream();this.sis = new SocketInputStream(this.input, 2048);parseConnection(socket);this.sis.readRequestLine(requestLine);parseHeaders();} catch (IOException e) {e.printStackTrace();} catch (ServletException e) {e.printStackTrace();}this.uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}private void parseConnection(Socket socket) {address = socket.getInetAddress();port = socket.getPort();
}private void parseHeaders() throws IOException, ServletException {while (true) {HttpHeader header = new HttpHeader();sis.readHeader(header);//表示读取完毕if (header.nameEnd == 0) {if (header.valueEnd == 0) {return;} else {throw new ServletException("httpProcessor.parseHeaders.colon");}}String name = new String(header.name, 0, header.nameEnd);String value = new String(header.value, 0, header.valueEnd);// 设置相应的请求头if (name.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.HOST_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.CONNECTION_NAME)) {headers.put(name, value);} else if (name.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {headers.put(name, value);} else {headers.put(name, value);}}
}

这个请求request  里边有各种方法 ,解析 i请求头,解析请求 路径 方式等,

其他的没有变化 ,然后 可以用上一节的main 方法 运行一下,结果是一样的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/814924.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

L1 【哈工大_操作系统】什么是操作系统

从本期开始&#xff0c;笔者将出一系列哈工大的《操作系统》课堂要点笔记&#xff0c;该课程应该算得上是国内最好的操作系统课程之一&#xff0c;也是哈工大CS课程含金量最高的课程之一。尤其是对于想学习国外课程《MIT 6.S081》《MIT 6.828》又基础不足的同学&#xff0c; 特…

Mathorcup 甲骨文识别

本资源主要包含第2-4问&#xff0c;第一问直接使用传统图像处理即可&#xff0c;需要有很多步骤&#xff0c;这一步大家自己写就行。 2 第2问&#xff0c;甲骨文识别 2.1 先处理源文件 原文件有jpg和json文件&#xff0c;都在一个文件夹下&#xff0c;需要对json文件进行处理…

[SystemVerilog]常见设计模式/实践

常见设计模式/实践 RTL 设计&#xff08;尤其是 ASIC&#xff09;的最终目标是制作出最小、最快的电路。为此&#xff0c;我们需要了解综合工具如何分析和优化设计。此外&#xff0c;我们还关注仿真速度&#xff0c;因为等待测试运行实际上是在浪费工程精力。虽然综合和仿真工…

YOLOv9最新改进系列:YOLOv9云服务器环境搭建-包百分百跑通!

YOLOv9最新改进系列&#xff1a;YOLOv9最新改进系列&#xff1a;YOLOv9云服务器环境搭建-包百分百跑通&#xff01; YOLOv9原文链接戳这里&#xff0c;原文全文翻译请关注B站Ai学术叫叫首er B站全文戳这里&#xff01; v8 v9 详细的改进教程以及源码&#xff0c;戳这&#x…

基于STC12C5A60S2系列1T 8051单片机的带字库液晶显示器LCD12864数据传输并行模式显示图像应用

基于STC12C5A60S2系列1T 8051单片机的液晶显示器LCD12864显示图像应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD12864简单介绍一、LCD12864点阵型液…

【面试】项目经理常见面试题

1、项目经理的能力和职能&#xff1f; 作为一个项目经理&#xff0c;我的主要能力和职能包括以下几个方面&#xff1a; 项目规划与管理&#xff1a;根据项目需求制定详尽的项目计划&#xff0c;包括时间表、资源分配、预算控制以及风险管理计划。确保项目在预定时间内按照既定…

2024最方便申请SSL证书方法介绍

申请SSL证书其实就像你去官方机构办个身份证&#xff0c;证明你的网站是合法且安全的。这里给你白话一点的简单步骤&#xff1a; 步骤一&#xff1a;确定需求 1. 域名&#xff1a;确保你有一个要申请证书的域名&#xff0c;就是你的网站地址&#xff0c;比如 www.example.com。…

MySQL Innodb中 可重复读隔离级别是否能完全规避幻读

一、MySQL 可重复读隔离级别下的幻读 在 MySQL Innodb引擎可重复读隔离级别下&#xff0c;已经尽可能最大程度的规避幻读的问题了&#xff0c;使得大多数情况下&#xff0c;重复读都是可以得到一致的结果。 针对于读数据&#xff0c;可以大致分为两种模式&#xff0c;快照读&…

网络篇12 | 链路层 ARP

网络篇12 | 链路层 ARP 01 简介1&#xff09;工作过程2&#xff09;ARP缓存2.1 动态ARP表项2.2 静态ARP表项2.3 短静态ARP表项2.4 长静态ARP表项 02 ARP报文格式1&#xff09;ARP请求报文格式2&#xff09;ARP响应报文格式3&#xff09;套一层以太网帧&#xff08;ARP帧&#x…

熟悉数电知识

23.数电 1. 建立时间、保持时间 建立时间setup time&#xff1a;时钟上升沿到来之前&#xff0c;输入端数据已经来到并稳定持续的时间。 保持时间hold time&#xff1a;时钟上升沿到来之后&#xff0c;传输端数据保持稳定并持续的时间。 2.二分频电路 每当输入一个时钟信号…

关于时频分析的一些事-答知乎问(一)

从信号的时频谱图中可以提取什么特征&#xff1f; 基于时频谱图的特征一般包括能量特征、时域和频域拓展特征以及时频内禀特征。 基于时频图的能量特征 基于时频图的特征中&#xff0c;能量特征是最简单的一种&#xff0c;通过分析时频谱图中的能量分布特性而获取信号的时频…

c-结构体内存对齐,位段

首先就是解释为什么要这么处理&#xff1a;处理器在处理已经对齐的变量时只需要一次就能够读取&#xff0c;而没对齐时可能就需要将该变量读取两次&#xff0c;&#xff08;既4个字节&#xff0c;读了前三个字节&#xff0c;还剩一个字节就需要再读取一次&#xff09; 接着说一…

Gitlab全量迁移

Gitlab全量迁移 一、背景1.前提条件 一、背景 公司研发使用的Gitlab由于服务器下架需要迁移到新的Gitlab服务器上。Gitlab官方推荐了先备份然后再恢复的方法。个人采用官方的另外一种方法&#xff0c;就写这篇文章给需要的小伙伴参考。 源Gitlab: http://old.mygitlab.com #地…

算法库应用- 表的自然链接

功 能: 设计算法,将两个单链表数组的特定位序, 相同者,链接起来 编程人: 王涛 详细博客:https://blog.csdn.net/qq_57484399/article/details/127161982 时 间: 2024.4.14 版 本: V1.0 V1.0 main.cpp /***************************************** 功 能: 设计算法,将两个…

Linux:环境基础开发工具使用

文章目录 前言1.Linux下的软件安装1.1 什么是软件包1.2 如何安装软件1.3 如何卸载软件 2.vim2.1 vim的基本概念2.2 vim的基本操作2.3 vim正常模式命令集2.4 vim末行模式命令集2.5 vim的操作总结 3.Linux下的编译器&#xff1a;gcc3.1 gcc的使用3.2 gcc是如何工作的3.2.1 预处理…

嵌入式学习54-ARM3(中断和时钟)

S3c2440中断控制器 内部外设&#xff1a; DMA &#xff1a;&#xff08;直接内存存取&#xff09; Direct Memor…

git 批量更改提交者邮箱规避 GH007 private email address 问题

问题描述 有时我们在推送提交时&#xff0c;会看到如下报错 remote: error: GH007: Your push would publish a private email address. remote: You can make your email public or disable this protection by visiting: remote: http://github.com/settings/emails这是因为…

基于Linux定时任务实现的MySQL周期性备份

1、创建备份目录 sudo mkdir -p /var/backups/mysql/database_name2、创建备份脚本 sudo touch /var/backups/mysql/mysqldump.sh# 用VIM编辑脚本文件&#xff0c;写入备份命令 sudo vim /var/backups/mysql/mysqldump.sh# 内如如下 #!/bin/bash mysqldump -uroot --single-…

Ant Design 官方推荐的实用前端工具

Ant Design 作为一款备受欢迎的 UI 组件库&#xff0c;不仅功能强大&#xff0c;还非常注重用户体验。在官网上还特别推荐了一系列其他实用的工具库&#xff0c;这些工具库能够与 Ant Design 形成互补&#xff0c;提供更为全面和高效的解决方案。通过结合这些工具&#xff0c;可…

数据库的负载均衡,高可用实验

一 高可用负载均衡集群数据库实验 1.实验拓扑图 2.实验准备(同一LAN区段)&#xff08;ntp DNS&#xff09; 客户端&#xff1a;IP&#xff1a;192.168.1.5 下载&#xff1a;MariaDB 负载均衡器&#xff1a;IP&#xff1a;192.168.1.1 下载&#xff1a;keepalived ipvsadm I…