Request Objects
在默认的连接器中org.apache.catalina.Request接口代表了一个HTTP请求对象。HttpRequest的父类—RequestBase类直接实现了这个接口。最终的实现是HttpRequestImpl,它继承HttpRequest。在第三章中,有facade类:RequstFacade和HttpRequestFacade。
Response Objects
Processing Requests
现在,你已经了解了request、response对象和HttpConnector对象怎么创建它们。这部分我们关注HttpProcessor类的process方法,这个方法是HttpProcessor类的被指配了一个socket后的run方法里调用。这个process方法处理一下工作:
解析连接
解析请求
解析头部信息
process方法使用boolean变量ok来表示在处理过程中是否出错和使用boolean变量finishResponse来表示Response接口的finishResponse方法应该被调用。
boolean ok = true;
boolean finishResponse = true;
此外,http11.keepAlive表明了这个连接时持久的,stopped表明HttpProcessor实例连接器
process方法也应该停止,http11表明一个web客户端到来HTTP请求从支持HTTP1.1。
象第三章,一个SocketInputStream实例被用来包装socket的输入流。注意:SocketInputStream的构造函数也传递从连接器得到的buffer size,不是从HttpProcessor类的一个局部变量得到的。这是因为HttpProcessor不能被默认连接器的用户访问。通过把buffer size放进Connector接口,这就允许任何一个人使用连接器来设置buffer size。
OutputStream output = null;
// Construct and initialize the objects we will need
try {
input = new SocketInputStream(socket.getInputstream(),
connector.getBufferSize());
}
catch (Exception e) {
ok = false;
}
然后,有一个while循环保持读取输入流,知道HttpProcessor停止、一个异常抛出或者是连接被关闭。
keepAlive = true;
while (!stopped && ok && keepAlive) {
...
}
在这个while循环里,process方法在finishResponse被设为true、获取输出流、处理一些request和resposne对象的初始化后开始执行。
finishResponse = true;
try {
request.setStream(input);
request.setResponse(response);
output = socket.getOutputStream();
response.setStream(output);
response.setRequest(request);
((HttpServletResponse) response.getResponse()).setHeader
("Server", SERVER_INFO);
} catch (Exception e) {
log("process.create", e); //logging is discussed in Chapter 7
ok = false;
}
后来,process方法通过调用parseConnection,parseRequest和parseHeader方法来解析到来的HTTP请求。
try {
if (ok) {
parseConnection(socket);
parseRequest(input, output);
if (!request.getRequest().getProtocol()
.startsWith("HTTP/0"))
parseHeaders(input);
parseConnection方法获取协议的值。可以使:HTTP0.9,HTTP1.0或者是HTTP1.1。如果协议是HTTP1.0,keepAlive的boolean值设置成false,因为HTTP1.0不支持持久连接。parseHeader方法设置sendAck的boolean值为true(但在100-continue头信息被HTTP请求发现除外)
如果协议时HTTP1.1,如果web客户端发送了100—continue头部信息,它还要检查块编码是否允许。
if (http11) {
// Sending a request acknowledge back to the client if
// requested.
ackRequest(output);
// If the protocol is HTTP/1.1, chunking is allowed.
if (connector.isChunkingAllowed())
response.setAllowChunking(true);
}
AckRequest方法检查sendAck的值和在sendAckweitrue时发送下面的字符串:
HTTP/1.1 100 Continue\r\n\r\n
在解析HTTP请求期间,可能会抛出异常。任何异常将设置ok或finishResponse为false。在解析后,process方法把request和response对象传递给容器的invoke方法。
try {
((HttpServletResponse) response).setHeader
("Date", FastHttpDateFormat.getCurrentDate());
if (ok) {
connector.getContainer().invoke(request, response);
}
}
最后,如果finishResponse任然为true,调用response对象的finishResponse方法和request对象的finishRequest方法,把输出flush。
if (finishResponse) {
...
response.finishResponse();
...
request.finishRequest();
...
output.flush();
while循环的最后一部分是检查response的Connection头部信息在servlet或HTTP1.0协议中是否被设置成colse。如果是这种情况,keepAlive被设置成false。同样的,request和response对象被回收。
if ( "close".equals(response.getHeader("Connection")) ) {
keepAlive = false;
}
// End of request processing
status = Constants.PROCESSOR_IDLE;
// Recycling the request and the response objects
request.recycle();
response.recycle();
}
While循环如果keepAlive是true,在之前的解析和容器的invoke方法没有错误,或者HttpProcessor实例没有停止。shutdownInput方法被调用,关闭socket。
try {
shutdownInput(input);
socket.close();
}
...
shutdownInput方法检查是否有没有读取的字节。如果有,跳过这些字节。
Parsing the Connection
parseConnection方法包含来自socket的Internet地址,并把它指配给HttpRequestImpl对象。它也检查代理是否被使用,并把它指配给request对象。
private void parseConnection(Socket socket)
throws IOException, ServletException {
if (debug >= 2)
log(" parseConnection: address=" + socket.getInetAddress() +
", port=" + connector.getPort());
((HttpRequestImpl) request).setInet(socket.getInetAddress());
if (proxyPort != 0)
request.setServerPort(proxyPort);
else
request.setServerPort(serverPort);
request.setSocket(socket);
}
Parsing the Request
parseRequest方法跟第三章的方法相似。
Parsing Headers
在默认连接器的parseHeaders方法使用org.apache.catalina.connector.http包中的HttpHeader和DefaultHeaders类。HttpHeader类代表一个HTTP请求头部信息。第三章使用字符串。HttpHeader类使用字符数组来避免字符串操作的开销。DefaultHeaders类是一个final类,它在字符数组中包含标准的HTTP请求头部信息。
static final char[] AUTHORIZATION_NAME =
"authorization".toCharArray();
static final char[] ACCEPT_LANGUAGE_NAME =
"accept-language".toCharArray();
static final char[] COOKIE_NAME = "cookie".toCharArray();
...
parseHeaders方法包含一个while循环,保持读取HTTP请求,直到没有更多的头部信息可读取。while循环调用request对象allocateHeader方法来获取一个空的HttpHeader实例后开始。这个实例被传递给SocketInputStream的readHeader方法。
HttpHeader header = request.allocateHeader();
// Read the next header
input.readHeader(header);
如果所有头部信息被读取,readHeader方法
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException
(sm.getString("httpProcessor.parseHeaders.colon"));
}
}
如果有一个header name,这里就会有一个header value与之对应。
String value = new String(header.value, 0, header.valueEnd);
接下来,就象第三章,parseHeaders方法让header name和在DefaultHeaders的标准name相比较。
注意:是在两个字符数组比较,而不是字符串。
if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
request.setAuthorization(value);
}
else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
parseAcceptLanguage(value);
}
else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
// parse cookie
}
else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
// get content length
}
else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
request.setContentType(value);
}
else if (header.equals(DefaultHeaders.HOST_NAME)) {
// get host name
}
else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
keepAlive = false;
response.setHeader("Connection", "close");
}
}
else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))
sendAck = true;
else
throw new ServletException(sm.getstring
("httpProcessor.parseHeaders.unknownExpectation"));
}
else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
//request.setTransferEncoding(header);
}
request.nextHeader();
The Simple Container Application
ex04.pyrmont.core.SimpleContainer和ex04 pyrmont.startup.Bootstrap。
Listing 4.3: The SimpleContainer class
package ex04.pyrmont.core;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.File;
import java.io.IOException;
import javax.naming.directory.DirContext;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Cluster;
import org.apache.catalina.Container;
import org.apache.catalina.ContainerListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Logger;
import org.apache.catalina.Manager;
import org.apache.catalina.Mapper;
import org.apache.catalina.Realm;
import org.apache.catalina.Request;
import org.apache.catalina.Response;
public class SimpleContainer implements Container {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
public SimpleContainer() { }
public String getInfo() {
return null;
}
public Loader getLoader() {
return null;
}
public void setLoader(Loader loader) { }
public Logger getLogger() {
return null;
}
public void setLogger(Logger logger) { }
public Manager getManager() {
return null;
}
public void setCluster(Cluster cluster) { }
public String getName() {
return null;
}
public void setName(String name) { }
public Container getParent() {
return null;
}
public void setParent(Container container) { }
public ClassLoader getParentClassLoader() {
return null;
}
public void setParentClassLoader(ClassLoader parent) { }
public Realm getRealm() {
return null;
}
public void setRealm(Realm realm) { }
public DirContext getResources() {
return null;
}
public void setResources(DirContext resources) { }
public void addChild(Container child) { }
public void addContainerListener(ContainerListener listener) { }
public void addMapper(Mapper mapper) { }
public void addPropertyChangeListener(
PropertyChangeListener listener) { }
public Container findchild(String name) {
return null;
}
public Container[] findChildren() {
return null;
}
public ContainerListener[] findContainerListeners() {
return null;
}
public Mapper findMapper(String protocol) {
return null;
}
public Mapper[] findMappers() {
return null;
}
public void invoke(Request request, Response response)
throws IoException, ServletException {
string servletName = ( (Httpservletrequest)
request).getRequestURI();
servletName = servletName.substring(servletName.lastIndexof("/") +
1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classpath = new File(WEB_ROOT); string repository = (new URL("file",null,
classpath.getCanonicalpath() + File.separator)).toString();
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadclass(servletName);
}
catch (classNotFoundException e) {
System.out.println(e.toString());
}
servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((HttpServletRequest) request,(HttpServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
public Container map(Request request, boolean update) {
return null;
}
public void removeChild(Container child) { }
public void removeContainerListener(ContainerListener listener) { }
public void removeMapper(Mapper mapper) { }
public void removeMapper(Mapper mapper) { }
public void removoPropertyChangeListener(
PropertyChangeListener listener) {
}
}
这里只提供了SimpleContainer类的invoke方法的实现。因为默认的连接器将调用这个方法。invoke方法创建一个类加载器,加载servlet类,调用它的servlet的service方法。
Listing 4.4: The ex04.pyrmont.startup.Bootstrap class
package ex04.pyrmont.startup;
import ex04.pyrmont.core.simplecontainer;
import org.apache.catalina.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(string[] args) {
HttpConnector connector = new HttpConnector();
SimpleContainer container = new SimpleContainer();
connector.setContainer(container);
try {
connector.initialize();
connector.start();
// make the application wait until we press any key.
System in.read();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
Bootstrap类的main方法构建了一个org.apache.catalina.connector.http.HttpConnector的实例和一个SimpleContainer接口。然后调用连接器的setContainer方法把连接器和容器联系起来。接下来,它调用连接器的initialize和start方法。它将让连接器处理在8080端口上的任何HTTP请求。
第四章 完