在(一)的基础上增加了,一个连接器,负责解析HTTP请求头,使servlet实例能够获得到请求头,cookie和请求参数/值等信息。
下面是这个应用程序的类图:
Bootstrap类很简单,代码如下:
public final class Bootstrap{ public static void main(String[] args){ HttpConnector connector = new HttpConnector(); connector.start(); }}
就是负责启动连接器来监听客户端的连接,与(一)相比,这个启动的任务与监听连接的任务分割了开来。
而HttpConnector就是负责监听和处理这个连接的,在连接到来取得代表客户端的Socket之后,将其交给HttpProcessor。
从UML类图中可以看出,HttpProcessor要与ServletProcessor和StaticResourceProcessor组合在一起,那么究竟用那一个处理器去处理呢,就得先处理客户端的Request了,同样,这两个对象在到达程序员自己定义的Servlet之前已经实例化好,而在(二)里面就是HttpRequest和HttpRespose;那么在这之前先得做的工作就是parseRequest()和parseHeaders()了,它们都是HttpProcessor的私有函数。
下面来了解如何解析HTTP请求,主要分层五个部分:
读取套接字输入流;
解析请求行;
解析请求头
解析Cookie
获取参数
分别解释下上面的步骤:
获取套接字就是为了后面的使用而准备的,这里用org.apache.catalina.connector.http.SocketInputStream来包装以下原始的InputStream,用SocketInputStream的好处就是方便使用它的readRequestLine()方法和readHeader()方法。
解析请求行就是在parseRequest(SocketInputStream sis, OutputStream out)方法里面进行的,它解析出了HTTP请求的方法,URI,协议版本,还有查询字段(如果有的话),并将相应的信息填充到HttpRequest对象当中。这个原始的URI可能是相对路径,类似 /hostname/someResource,也可能是绝对路径,类似http://www.hostname.com/somResource,当然啦,做一些判断就OK,最后,规范化以下请求的URI
请求头信息有HttpHeader类表示,它在创建了之后传给SocketInputStream的readHeader()方法,若是有头信息可以读取,就将其填充。在parseHeaders()方法中,会有一个while循环不断读取HttpReader信息,如果读取到了,就调用request.addHeader(name, value)进行设置,如果没有了那么HttpHeader实例的属性字段nameEnd和nameValue都是0,由于这个InputStream是不支持读写位置的随意移动的,因此读取的方向会一直往下。
Cookie是作为请求头的一部份发送的,字段类似 cookie:username=biu;password=qwert; 解析Tomcat提供的org.apache.catalina.util.RequestUtil类的parseCookieHeader()方法完成,这个方法放回一个Cookie[],每一个元素对于cookie中分号的一个字段。具体就是在上面的那个while循环中加一个if条件判断语句来处理
解析字段其实不是在这一步就完成的。而是在用户自定义的Servlet中调用getParameter(), getParameterMap(), getParameterNames(), getParameterValues()之后才进行的。这样有一个好处就是,减少解析参数带来的延迟,提高响应速度。参数值需要解析一次,而且也只会解析一次,同时不允许程序员去修改。比如说ParameterMap类,它是一个HashMap,利用一个boolean属性lock来限制修改操作。简单的示例如下:
public final class ParameterMap extends HashMap{ private boolean locked = false; /* 一系列构造函数 */ public ParameterMap(){ super(); } /* 比如说下面这个方法 */ public Object put(Object key, Object value){ if(locked) throw new IllegalStateException(sm.getString("parameterMap.locked")); return super.put(key, value); } }
那么是如何控制只解析一次的呢?原因在parseParameters()方法中也有一个boolean字段parsed,当解析完成,参数会存储到对象变量parameters中,
if(parsed) return;
然后这个方法内部会创建一个名为result的ParameterMap变量,将其指向遍历parameters,若paramters==null,则会创建一个新的对象;接着就打开那个ParameterMap的锁了,setLocked(false);然后检查编码,就可以进行解析工作了,别忘了最后还有setLocked(true);
一个简单的Servlet容器就是初步这样~