03【HowTomcatWork】手写一个连接器,更完整的http请求、响应包的处理过程

128 阅读3分钟

代码仓库 本节对应05-connector

@[toc]

本节概述

05-connector中,我们把之前写的 HttpServer 类被分离为两个类:HttpConnector和 HttpProcessor

之前的 HttpServer类的职责是【等待 HTTP 请求】并【创建请求和响应对象】。 在本章的应用中,等待 HTTP 请求的工作交给 HttpConnector 实例,而创建请求和响应对象的工作交给了HttpProcessor 实例。

以前,我的请求类是实现了javax.servlet.ServletRequest接口的Request类。 本章中,我的请求类是实现了 javax.servlet.http.HttpServletRequest 接口的 HttpRequest类。

【http请求包中的信息被解析后】会封装成一个 HttpRequest 对象,它会被转换为一个 HttpRequestFacade 实例, 传递给 servlet 的 service 方法。 在这里插入图片描述

解析一个 HTTP请求牵涉昂贵的字符串和其他操作,假如只是解析 servlet 需要的值的话,连接器就能节省许多 CPU 周期。 本章自己写的HttpConnector连接器和【Tomcat的默认连接器】试图不解析请求参数,直到 servlet 真正需要它的时候再解析,通过这样来获得更高效率。后文解释。

处理请求

05-connector模块中,我写了一个HttpProcessor类, 它使用SocketInputStream类来从套接字的InputStream中读取字节流。一个 SocketInputStream 实例对【从套接字的 getInputStream 方法中返回的java.io.InputStream 实例】进行包装。 在这里插入图片描述

SocketInputStream 类提供了两个重要的方法:readRequestLine 和 readHeader。 readRequestLine读取请求行,readHeader读取请求头 我们知道一个http请求分为请求行,请求头,请求空行和请求体。

readRequestLine 返回一个 HTTP 请求的第一行。例如,这行包括了 URI,方法和 HTTP 版本,这些信息封装在HttpRequestLine对象中。

readHeader每次被调用来获得【一个请求头的键值对】,这1个键值对的被封装成HttpHeader对象,并且应该被重复的调用直到所有的请求头键值对被读取到。

下图是访问【http://localhost:8090/index.html?name=cy&age=20】经过解析后的HttpRequest对象。HttpRequest对象大多数信息都存在了,但是请求参数还没有解析。

在这里插入图片描述

啥时候解析请求参数? 当getParameter方法被调用 ,会解析请求参数 我们可以在servlet的service方法中调用getParameter方法

在这里插入图片描述

解析参数的过程分2步

在这里插入图片描述

响应处理

原来我们通过socket.getOutputStream() 得到一个java.net.SocketOutputStream对象,用它的write()方法发送字节数据。

后来我们做了一定优化,将java.net.SocketOutputStream对象包装成java.io.PrintWriter对象发送数据

java.io.PrintWriter对象有一个bool属性autoFlush autoFlush为true:println、printf、format方法调用后会自动清除缓冲区

如果是一次servlet请求,我们应该先通过【response.sendHeaders()】发送响应行,响应头,响应空行的数据 在这里插入图片描述

public void sendHeaders() 和之前直接将java.net.SocketOutputStream对象包装成java.io.PrintWriter对象不同 我们这里指定了字符集

在这里插入图片描述

发送响应行、响应头

在这里插入图片描述

响应到达servlet

之前已经发送响应行、响应头、响应空行 在servlet中,我们也使用PrintWriter继续向【响应体】写数据,当访问地址 http://localhost:8090/servlet/PrimitiveServlet?name=cy 控制台会输出cy, 浏览器显示Hello

在这里插入图片描述