前言:
最近思考怎么提升自己的编码水平,看的是比较多了,看了一些 Java 的源码实现,确实几乎没有重复的代码,封装与抽象做的很漂亮。但是学习不能光看,也得动手。
之前看过一个理论,就是哪怕把常用的框架整个复制一遍,自己写一遍,也会有很多收获,所以我打算把常用的 Java
框架的核心功能自己实现一遍,这样代码量不会太大,实现了核心功能也会有不小的收获。
第一个目标瞄准了 HTTP Server
,作为一个 Java Web
开发者,基本上天天与这东西打交道,从最开始的外置 Tomcat
运行 war
包,到后面的 Springboot
使用的内置 Tomcat
,都是作为一个服务器来响应客户端的请求。
这里 Tomcat
是一个 Servlet
容器,比普通的 HTTP 服务器
要复杂,因为不仅包含了 HTTP
服务器的功能,还作为了 Servlet
的容器,需要实现 Servlet
相关的接口。
这里我的目的就是跟着 B站 尚学堂的课程,实现一个自己的 HTTP
服务器,这个是我前阵子跟着写的,一路跟下来,我发现了不少可以改进的地方,比如使用 Java8 的 API 替换繁杂的 Collection
操作,以及深入学习 Java 对于 TCP/IP
底层的接口的实现,以及 Socket
相关的类的源码的学习。
虽然这是一个挺简单的项目,我用了一天半整个过了一遍,不过说实话收获真的不算少,对于 Tomcat 的文件结构以及 Servlet 有了更深入的理解。
学习编程就是这样,当你学习过程中可能涉及到了很多相关的知识,所以需要构建自己的知识地图,这样才能触类旁通,越学越快。
学习目标:使用 Java 编写一个 HTTP SERVER
参考资料: B站尚学堂百战程序员视频
编写一个基于 请求-响应 的服务器
需要用到的知识:
OOP 面向对象编程思想
容器(集合类)
IO
多线程
网络编程
XML 解析
反射
HTML
HTTP 协议
前置知识1 —— 反射:赋予了 Java 语言动态性
为什么这里需要反射知识?
在编写 Servlet
时,我们需要引入 web.xml
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>study01.server06.basic.servlet.LoginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>reg</servlet-name>
<servlet-class>study01.server06.basic.servlet.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
<url-pattern>/g</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>reg</servlet-name>
<url-pattern>/reg</url-pattern>
</servlet-mapping>
</web-app>
Servlet 容器
需要根据 web.xml
配置中配置的类的全路径名来动态生成对应 Servlet
的实例,这就需要使用到反射技术来 在运行期间动态创建对象 。
前置知识2 —— XML 解析
读取 XML 配置文件的前提就是对其进行解析,Java 主要有两种方式 DOM 和 SAX,这里使用 SAX,下面是简单介绍以及对应的 Demo。
项目中解析 XML 的应用思路:解析 web.xml
,根据 servlet-mapping
的 url-parttern
找到 servlet-class
,然后根据完整的类路径使用反射构造对应的类
3. 封装Web上下文 —— WebContext 类编写
解析完 web.xml
之后,需要根据 url-pattern
找到 servlet-name
,然后找到对应的 servlet-class
,并初始化对应的类,此时需要使用 Map
保存这些对象。
WebContext 核心代码:
public class WebContext {
//key-->servlet-name value -->servlet-class
private List<Entity> entities = null;
//key -->url-pattern value -->servlet-name
private List<Mapping> mappings = null;
private Map<String, String> entityMap = new HashMap<>();
private Map<String, String> mappingMap = new HashMap<>();
public WebContext(List<Entity> entities, List<Mapping> mappings) {
this.entities = entities;
this.mappings = mappings;
//todo 将 entity 对应的 List 转成对应的 Map 这里可以用 Java8 的写法
for (Entity entity : entities) {
entityMap.put(entity.getName(), entity.getClz());
}
for (Mapping mapping : mappings) {
for (String pattern : mapping.getPatterns()) {
mappingMap.put(pattern, mapping.getName());
}
}
}
/**
* 从 pattern 中找到对应的 name 并且获取到对应 name 的具体类全路径
*
* @param pattern url
* @return class full name
*/
public String getClz(String pattern) {
String name = mappingMap.get(pattern);
return entityMap.get(name);
}
}
4.HTTP服务器的编写思路:
1、了解 HTTP 协议 ---> 手写HTTP服务器 —— HTTP 协议简介
2、具体使用到的是 java.net 包下的类,也就是 Java 中的网络编程相关知识 ---> Java 网络编程概述
- 因为要编写一个 HTTP 服务器,就需要能够接受 HTTP 请求,那么就一定要了解 HTTP 的格式,才能对应的进行正确的解析。
Socket
编程的特点:服务器与浏览器 建立连接
,然后通过不同的协议发送数据。
目标:
- 使用
ServerSocket
与浏览器建立连接,并获取请求协议。 - 返回响应协议
返回给浏览器响应内容的步骤:
- 准备内容
- 获取字节数长度
- 拼接响应协议
- 使用输出流输出
这里使用到的 JDK
的类是 java.net
包下的 Socket
相关的类,这个包是 Java
中网络相关的包,之前也比较陌生,借着这个机会顺便梳理一下 java.net
包的结构:
ServerSocket
: 充当服务器角色的套接字实现类Socket
: 充当客户端角色的套接字实现类。
这里通过使用 ServerSocket
绑定一个端口,客户端可以通过这个端口发送请求给服务器。
已经成功建立连接:
封装 Response 对象,只关注返回的内容
上一步使用 ServerSocket 已经可以接收 HTTP 请求了,但是并没有返回给浏览器任何数据,所以这一步开始返回数据给浏览器。
返回数据需要返回 Response
对象,这里面封装了 HTTP
响应数据规定的 :
HTTP 协议版本号
状态码
状态信息,服务器自己定义
HTTP 响应头
可选项 ,数据主体
下面是相关代码: 可以看到这里返回的数据主体和Response 相关的格式都是手动拼接的,并且下面的拼接 HTTP 响应固定信息不应该与业务逻辑耦合在一起。
有 Response 的 ServerSocket:
所以需要将 Response
逻辑单独封装起来,解除与业务逻辑之间的耦合:
Response 类中的主要逻辑:构建响应固定格式信息
封装 Response 后的 service 方法: 所有构建 Response 固定格式信息的步骤都被封装,只需要传入本次的返回码即可。
封装 Request
将请求字符串转为对象
快速获取请求参数
HTTP 协议的意义
固定的格式,通过大量的对字符串的操作,就可以对请求进行处理,然后返回。
GET 方法需要对中文进行 decode 转为 utf8 字符格式
引入 Servlet,解耦业务代码
整合 web.xml 使用反射动态创建 Servlet
引入多线程
- 每次请求起一个线程,然后创建对于的
Servlet
进行处理 - 编写
DisPatcherServlet
进行分发
服务器中的链接特性:
- 长连接
- 短链接
问题
1、Maven/Web 项目中 classpath 读取 Resouce 资源
2、IDEA 断点调试技巧 ---- 静态代码块中的代码必须要一个个打断点才能一步步 debug ,否则会被直接跳过
Learning Poin
- ServerSocket 源码 ----> 背后是 Java 中 net 包的作用以及设计理念
总结:
封装了请求、响应,Servlet 的分发,使用反射动态从配置文件中生成 Servlet,底层是 java.net 包中的 ServerSocket 类,实现了 WebServer 的基础功能,也有很多细节值得继续挖掘。
Q.E.D.
Comments | 0 条评论