代码仓库 本节对应05-connector
本节概述
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
|