连接器
先说一句题外话,上一章中的servlet容器有一个问题:运行以下的servlet时,
public void service(ServletRequest req, ServletResponse resp) throws IOException{ PrintWriter pw=resp.getWriter(); pw.println("hello,this is row1"); pw.print("hello,this is row2"); }
只会显示:hello,this is row1。
为了解决这个问题,做了以下改动:首先,由一个普通的Request/Response对象变为规范的HttpServletRequest/HttpServletResponse
public class HttpResponse implements HttpServletResponse { OutputStream output; public HttpResponse(OutputStream output) { this.output = output; } /** * call this method to send headers and response to the output */ public void finishResponse() { // sendHeaders(); // Flush and close the appropriate output mechanism if (writer != null) { writer.flush(); writer.close(); } } public PrintWriter getWriter() throws IOException { ResponseStream newStream = new ResponseStream(this); newStream.setCommit(false); OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding()); writer = new ResponseWriter(osr); return writer; } }
而之前的实现为
public PrintWriter getWriter() throws IOException { // autoflush is true, println() will flush, // but print() will not. writer = new PrintWriter(output, true); return writer; }
而且之前的ServletProcessor在调用完service方法后额外增加了一行:
((HttpResponse) response).finishResponse();
创建一个 HttpRequest 对象
HttpRequest类的很多方法都留空(你需要等到第4章才会完全实现),但是servlet程序员 已经可以从到来的 HTTP 请求中获得头部,cookies 和参数。这三种类型的值被存储在下面几个 引用变量中:
protected HashMap headers = new HashMap(); protected ArrayList cookies = new ArrayList(); protected ParameterMap parameters = null;
因为 HTTP 请求的解析是一项相当复杂的任务,分为以下内容:
- 读取套接字的输入流
- 解析请求行
- 解析头部
- 解析 cookies
- 获取参数
读取套接字的输入流
SocketInputStream input = null; OutputStream output = null; try { input = new SocketInputStream(socket.getInputStream(), 2048); ...
拥有一个 SocketInputStream 是为了两个重要方法:readRequestLine和 readHeader,而在上一章中直接是直接使用的Socket的InputStream
解析请求行
HttpProcessor 的 process 方法调用私有方法 parseRequest 用来解析请求行,例如一个 HTTP 请求的第一行。这里是一个请求行的例子:
GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
请求行的第二部分是 URI 加上一个查询字符串。在上面的例子中,URI 是这样的:
/myApp/ModernServlet
不过,大多数情况下,URI 指向一个相对资源,URI 还可以是一个绝对值,就像下面所示:
http://www.brainysoftware.com/index.html?name=Tarzan
在 servlet/JSP 编程中,参数名 jsessionid 是用来携带一 个会话标识符。会话标识符经常被作为 cookie 来嵌入,但是程序员可以选择把它嵌入到查询字符串去,例如,当浏览器的 cookie 被禁用的时候。
解析头部
一个HTTP头部是用类HttpHeader来代表的。
一些头部也需要某些属性的设置。例如,当 servlet 调用 javax.servlet.ServletRequest
的 getContentLength 方法的时候,content-length 头部的值将被返回。而包 cookies 的 cookie 头部将会给添加到 cookie 集合中。
解析 Cookies
Cookies 是作为一个 Http 请求头部通过浏览器来发送的。这样一个头部名为"cookie"并且 它的值是一些 cookie 名/值对。这里是一个包括两个 cookie:username 和 password 的 cookie 头部的例子。
Cookie: userName=budi; password=pwd;
另外系统会创造一个名为JSESSIONID的输出Cookie,或称为"Session Cookie",这个cookie也需要单独处理
获取参数
参数可以在查询字符串或者请求内容里边找到。假如用户使用 GET 方法来请求 servlet 的话, 所有的参数将在查询字符串里边出现。
在当用户使用POST方法发送请求的时候,内容长度大于零,并且内容类型是 application/x-www-form-urlencoded 的时候。从内容中解析参数
创建一个 HttpResponse 对象
HttpResponse 类实现了 javax.servlet.http.HttpServletResponse。跟随它的是一个叫做 HttpResponseFacade 的 façade 类
通过 OutputStreamWriter,写进去的字符通过一种特定的字符集被编码成字节。这种字符 集可以使用名字来设定,或者明确给出,或者使用平台可接受的默认字符集。write 方法的每次 调用都会导致在给定的字符上编码转换器的调用。在写入底层的输出流之前,生成的字节都会累 积到一个缓冲区中。缓冲区的大小可以自己设定,但是对大多数场景来说,默认的就足够大了。 注意的是,传递给 write 方法的字符是没有被缓冲的。
总结
在本章中,你已经知道了连接器是如何工作的。建立起来的连接器是 Tomcat4的默认连接器的简化版 。正如你所知道的,因为默认连接器并不高效,所以已经被弃用了。例如,所有的 HTTP 请求头部都被解析了,即使它们没有在 servlet 中使用过。因此,默认连接器很慢,并且 已经被 Coyote 所代替了。Coyote 是一个更快的连接器,它的源代码可以在 Apache 软件基金会的网站中下载。不管怎样,默认连接器作为一个优秀的学习工具,将会在第4章中详细讨论。
浙公网安备 33010602011771号