|
The Connector(连接器)
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。这些方法将在下面部分讨论。
Creating an HttpRequest Object
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
Reading the Socket's Input Stream
在第一章和第二章你做了请求解析。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。
Parsing the Request Line
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 + "'");
} |
|