Coder 爱翻译 How Tomcat Works 第三章 第二部分

[size=x-large]The Connector(连接器)[/size]

HttpConnector类代表了一个负责创建一个等待HTTP请求的服务器socket连接器。
HttpConnector类实现了java.lang.Runnable接口,所以它可以当做自己的一个独立的线程。当你启动这个应用程序,一个HttpConnector的实例就被创建,然后它执行它的run方法。

run方法包含一个while循环来处理下面的事情:
 等待HTTP请求
 为每一个请求创建一个HttpProcessor实例
 调用这个HttpProcessor的process方法

你可以看到,这HttpConnector类和之前的HttpServer1类很相像。除了当从java.net.ServerSocket的accept方法获得了一个socket后,一个HttpProcessor实例被创建,和调用process方法,传递socket。

注意:HttpConnector类的另外一个方法getScheme是返回scheme(HTTP)。

HttpProcessor类的process方法接收一个从HTTP请求来的socket。对于每一个来访的HTTP请求,做了一下事情:
 创建一个HttpRequest对象。
 创建一个HttpResponse对象
 解析HTTP请求的第一行、头信息和填充HttpRequest对象。
 传递HttpRequest和HttpResponse对象给ServletProcessor或StaticResourceProcessor。ServletProcessor调用被请求的servlet的service方法。StaticResourceProcessor发送静态资源内容。

Listing 3.3: The HttpProcessor class's process method.

public void process(Socket socket) {
SocketInputStream input = null;
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();
// create HttpRequest object and parse
request = new HttpRequest(input);
// create HttpResponse object
response = new HttpResponse(output);
response.setRequest(request);
response.setHeader("Server", "Pyrmont Servlet Container");

parseRequest(input, output);
parseHeaders(input);
//check if this is a request for a servlet or a static resource
//a request for a servlet begins with "/servlet/"
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new
StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
// no shutdown for this application
}
catch (Exception e) {
e.printStackTrace ();
}
}

Process方法在获取了socket的输入流和输出流后开始执行。注意:在这个方法我们使用继承自java.io.InputStream的SocketInputStream类。

SocketInputStream input = null;
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();

然后,它创建一个HttpRequest实例和一个HttpResponse实例,并把HttpRequest指配给HttpResponse。

// create HttpRequest object and parse
request = new HttpRequest(input);
// create HttpResponse object
response = new HttpResponse(output);
response.setRequest(request);

HttpResponse类在这章中的应用比前面第二章的Response类更加复杂。其一:你可以通过调用它的setHeader方法来发送头部信息到客户端。

response.setHeader("Server", "Pyrmont Servlet Container");

接下来,process方法调用HttpProcessor类的两个private方法来解析请求。

parseRequest(input, output);
parseHeaders (input);

后来,把HttpRequest和HttpReponse对象交给ServletProcessor还是StaticResourceProcessor处理取决于请求的URI。

if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
} else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}

最后,关闭socket。

socket.close();

注意HttpProcessor类使用org.apache.catalina.util.StringManager类来发送错误信息。

protected StringManager sm =
StringManager.getManager("ex03.pyrmont.connector.http");

HttpProcessor类中的private方法—parseRequest,parseHeader和normalize被调用来填充HttpRequest。这些方法将在下面部分讨论。

[size=x-large]Creating an HttpRequest Object[/size]

HttpRequest类实现了javax.servlet.http.HttpServletRequest接口。
在HttpRequest类有许多方法是空的。但是servlet程序员可以获得头部信息,cookies和HTTP请求的参数。

protected HashMap headers = new HashMap();
protected ArrayList cookies = new ArrayList();
protected ParameterMap parameters = null;

ParameterMap类将在“Obtaining Parameters”中讲解。

此外,一个servlet程序员可从javax.servlet.http.HttpServletRequest:getCookies,getDateHeader,
getHeader, getHeaderNames, getHeaders, getParameter, getPrameterMap, getParameterNames, 和getParameterValues方法获得正确的返回值。一旦你得到headers, cookies, 和被正确值填充的parameter时,相关方法的实现就很简单了。你可以看HttpRequest类。

不用说,这里最大的问题是解析HTTP请求和填充HttpRequest对象。对于headers 和cookies,HttpRequest类提供了addHeader和addCookie方法,可以在HttpProcessor的parseHeaders方法调用。参数在它需要的时候才会被解析,使用HttpRequest类的parseParameters方法。所有方法将在下面部分讲解。

HTTP请求解析是一个相当复杂的工作。把它分成下面几个部分:

 读取socket的输入流 - Reading the socket's input stream
 解析请求行 - Parsing the request line
 解析头部信息 - Parsing headers
 解cookie - Parsing cookies
 获取参数 - Obtaining parameters

[size=x-large]Reading the Socket's Input Stream[/size]

在第一章和第二章你做了请求解析。ex01.pyrmont.HttpRequest和ex02.pyrmont.HttpRequest 类。你获得请求行,获取方法(GET…),URI和HTTP版本。

byte[] buffer = new byte [2048];
try {
// input is the InputStream from the socket.
i = input.read(buffer);
}

你没有试图解析更多请求内容在前两个应用中。在这章的应用的,你使用ex03.pyrmont.connector.http.SocketInputStream类,一个org.apache.catalina.connector.http.
SocketInputStream类的复制。这个类不仅提供了获取请求行的方法,还有请求头部信息的方法。

你通过传递一个InputStream和代表buffer大小的integer整型数来构建一个SocketInputStream实例。在这个应用中,你在ex03.pyrmont.connector.http.HttpProcessor的process方法中创建一个SocketInputStream对象。下面是代码片段:

SocketInputStream input = null;
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
...

在前面提到的,拥有一个SocketInputStream的原因是2个很重要的方法:readRequestLine和readHeader。

[size=x-large]Parsing the Request Line[/size]

HttpProcessor的process方法调用private的parseRequest方法来解析请求行。例如:HTTP请求的第一行:

GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1

请求行的第二部分是URI加上可选择的查询字符串(optional query string)。例如:

/myApp/ModernServlet

查询字符串例子:

userName=tarzan&password=pwd

查询字符串可以获取0个或者更多的参数。在上面的例子中,有两个name/value的成对参数。userName/ tarzan和password/ pwd。在servlet/jsp编程中,参数名jsessionid被用来存储一个session的标识符。Session的标识符通常被嵌入到cookies,但是程序员可以选择把session标识符嵌入到查询字符串中。例如如果浏览器关闭了对cookies的支持。

当parseParameter方法在HttpProcessor类的process方法里调用时,请求变量指向一个HttpRequest实例。parseRequest方法解析请求行,获取一些值,并把这些值分配给HttpRequest对象。现在,我们来看看parseRequest方法:

Listing 3.4: The parseRequest method in the HttpProcessor class

private void parseRequest(SocketInputStream input, OutputStre
throws IOException, ServletException {
// Parse the incoming request line
input.readRequestLine(requestLine);
String method =
new String(requestLine.method, 0, requestLine.methodEnd
String uri = null;
String protocol = new String(requestLine.protocol, 0,
requestLine.protocolEnd);
// Validate the incoming request line
if (method, length () < 1) {
throw new ServletException("Missing HTTP request method");
}
else if (requestLine.uriEnd < 1) {
throw new ServletException("Missing HTTP request URI");
}
// Parse any query parameters out of the request URI
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}

// Parse any requested session ID out of the request URI
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match,length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}
else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}

// Normalize URI (using String operations at the moment)
String normalizedUri = normalize(uri);
// Set the corresponding request properties
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}
if (normalizedUri == null) {
throw new ServletException("Invalid URI: " + uri + "'");
}
}

ParseRequest方法在SocketInputStream类的readRequestLine方法调用后执行。

input.readRequestLine(requestLine);

requestLine是一个HttpProcessor 内部的HttpRequestLine的实例。

private HttpRequestLine requestLine = new HttpRequestLine();

调用它的readRequestLine方法告诉SocketInputStream来填充HttpRequestLine实例。

接下来,parseRequest方法获取了请求行的方法(GET…),URI和协议。

String method = new String(requestLine.method, 0, requestLine.methodEnd);
String uri = null;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

但是,这里可能在URI后面会有查询字符串。如果存在的话,查询字符串被一个“?”分割。此外,parseRequest方法试图获取查询字符串和调用setQueryString方法来填充HttpRequest对象。


// Parse any query parameters out of the request URI
int question = requestLine.indexOf("?");
if (question >= 0) { // there is a query string.
request.setQueryString(new String(requestLine.uri, question + 1,
requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
}
else {
request.setQueryString (null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}

但是,大多数是一个URI指向一个相关资源,一个URI也可以是一个绝对的值,例如:

http://www.brainysoftware.com/index.html?name=Tarzan

parseRequest方法也会检查:

// Checking for an absolute URI (with the HTTP protocol)
if (!uri.startsWith("/")) {
// not starting with /, this is an absolute URI
int pos = uri.indexOf("://");
// Parsing out protocol and host name
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}

然后,查询字符串也可以包含一个session标识符,通过jsessionid的参数名表明。parseRequest方法的也检查一个session的标识符。如果在查询字符串中找到jsessionid,这个方法获得session标识符,通过调用它的setRequestedSessionId方法来把这个值分配给HttpRequest实例。

// Parse any requested session ID out of the request URI
String match = ";jsessionid=";
int semicolon = uri.indexOf(match);
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
}

else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL (true);
uri = uri.substring(0, semicolon) + rest;
}
else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}

如果找到jsessionid,这也意味着session标识符在一个查询字符串中存储着。而不是在一个cookie中。传递true给request的setRequestSessionURL方法。反之,传递false给setRequestSessionURL方法,再传递null给setRequestSessionId方法。
这时,uri的值被jsessionid抽离出来。

然后,parseRequest方法传递uri给normalize方法来修正一个“异常”的URI。例如:任何“\”会被替换成“/”。如果uri格式正确或者这个异常被修正。normalize返回这个正确的uri或者被修正后的uri。如果URI不能被修正,它将会被认为不合法且normalize返回null。这种情况下(返回null),parseRequest方法会在方法最后抛出一个异常。

最后,parseRequest方法给HttpRequest对象设置属性:

((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}

此外,如果从normalize方法的返回值是null,方法抛出一个异常:

if (normalizedUri == null) {
throw new ServletException("Invalid URI: " + uri + "'");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值