一个tomcat的connector必须满足以下条件:
- 必须实现 org.apache.catalina.Connector 接口。
- 必须创建实现org.apache.catalina.Request接口的请求对象
- 必须创建实现org.apache.catalina.Response接口的响应对象
Tomcat 默认的连接器基本上和前面的简单连接器工作方式差不多。
它等待HTTP请求,创建请求和响应对象,传递这两个对象给容器。传递是通过or.apache.catalina.Container接口的invoke方法
public void invoke(
org.apache.catalina.Request request,
org.apache.catalina.Response response);
在invoke方法中,容器加载servlet类,调用它的service方法,管理会话,记录错误信息。
默认的连接器做了一些优化,首先就是提供了一个可变对象的池来避免对象创建的开销。其次,许多地方使用字符数组来代替字符串对象。
HTTP 协议的持久连接特性
当一个浏览器连接到一个web服务器,连接会在请求的资源发送后关闭。然而,一个网页可能还包含图片,applets小程序等资源。因此,当一个页面被请求,浏览器也需要下载被这个页面引用的资源。如果这些资源通过不同的连接去下载,处理就会变得非常慢。持久连接的引入解决了这个问题,当一个页面下载下来,服务器不会直接关闭连接。而是等待客户端请求所有的应用资源。使用相同的连接来下载所有的资源节省了大量的时间。
Connector 接口
默认连接器接口类图
连接器和容器是一对一的关系。
HttpConnector 类
它实现了org.apache.catalina.Connnector, java.lang.Runnable,org.apache.catalina.LifeCycle.生命周期接口用来维护所有实现它的Catalina组件。
创建一个服务器套接字
初始化方法中调用开放的私有方法来返回java.net.ServerSocket实例,赋值给serverSocket.
获得一个ServerSocket实例通过它的工厂。可以通过org.pache.catalina.net.ServerSocketfactory了解。
维护HttpProcessor的实例们
在第三章中,HttpConnector 实例一次只有一个HttpProcessor实例,它可以处理Http 请求一次。在默认的连接器中,HttpConnector有一个HttpProcessor对象池,每一个HttpProcessor实例有一个独有的线程。因此HttpConnector可以同时处理多个http 请求。
避免每次创建HttpProcessor对象,HttpProcessor实例都存放于java.io.Stack 中:
private Stack processors = new Stack();
通过两个变量来控制HttpProcessor的数量:
protected int minProcessors = 5;
private int maxProcessors = 20;
初始化时,HttpConnector创建minProcessors 个HttpProcessor实例,然后根据请求不停的创建,直到达到maxProcessors。
处理HTTP请求
HttpConnector类把主要的的逻辑放在run方法中,有一个while循环来等待socket
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
...
每一个HTTP 请求,它都会通过调用私有的createProcessor方法来获得HttpProcessor实例。
HttpProcessor processor = createProcessor();
if (processor == null) {
try {
log(sm.getString("httpConnector.noProcessor"));
socket.close();
}
...
continue;
如果processor实例超过上限,那么socket只是简单关闭,并不处理请求。
如果createProcessor方法不返回空,客户端套接字就被传递给processor。
processor.assign(socket);
现在就是HttpProcessor实例的任务了,它读取输入流和解析Http请求。
HttpProcessor 类
Listing 4.1: The HttpProcessor class's run method.
public void run() {
// Process requests until we receive a shutdown signal
while (!stopped) {
// Wait for the next socket to be assigned
Socket socket = await();
if (socket == null)
continue;
// Process the request from this socket
try {
process(socket);
}
catch (Throwable t) {
log("process.invoke", t);
}
// Finish up this request
connector.recycle(this);
}
// Tell threadStop() we have shut ourselves down successfully
synchronized (threadSync) {
threadSync.notifyAll();
}
}
run方法中的while循环按照这个顺序运行: 获得套接字,处理,调用连接器的recycle方法把当前的HttpProcessor实例推送回栈。
void recycle(HttpProcessor processor){
processors.push(processor);
}
这里有HttpProcessor类的assign 和 await方法:
synchronized void assign(Socket socket) {
// Wait for the processor to get the previous socket
while (available) {
try {
wait();
}
catch (InterruptedException e) {
}
}
// Store the newly available Socket and notify our thread
this.socket = socket;
available = true;
notifyAll();
...
}
private synchronized Socket await() {
// Wait for the Connector to provide a new Socket
while (!available) {
try {
wait();
}
catch (InterruptedException e) {
}
}
// Notify the Connector that we have received this Socket
Socket socket = this.socket;
available = false;
notifyAll();
if ((debug >= 1) && (socket != null))
log(" The incoming request has been awaited");
return (socket);
}
程序遵循两种方法综合
Table 4.1: Summary of the await and assign method
The processor thread (the await method)
while (!available) {
wait();
}
Socket socket = this.socket;
available = false;
notifyAll();
return socket; // to the run
// method
The connector thread (the assign method)
while (available) {
wait();
}
this.socket = socket;
available = true;
notifyAll();
...
Request and Response 类;
处理请求
- 解析连接
- 解析请求
- 解析头
Listing 4.2: The parseConnection method
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);
}
简单容器程序
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 setManager(Manager manager) { }
public Cluster getCluster() {
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[] findContainerList
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)).toStrin
urls[0] = new URL(null, repository, streamHand
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 removoPropertyChangeListener(
PropertyChangeListener listener) {
}
}
Bootstrap 类
The Bootstrap class is given in Listing 4.4.
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();
}
}
}
Running 这个程序。
java -classpath ./lib/servlet.jar;./ ex04.pyrmont.startup.Bootstrap