前言
上篇文章中讲到了 Connector 的初始化与启动,其中最关键的就是 ProtocolHandler 的初始化与启动。tomcat 中 ProtocolHandler 的默认实现类是 Http11NioProtocol。tomcat9.0.16 中 ProtocolHandler 的实现类中还有一个 Http11Nio2Protocol,两者实现上类似。这两个实现的的父类都是 AbstractHttp11JsseProtocol,AbstractHttp11JsseProtocol 的父类是 AbstractHttp11Protocol,AbstractHttp11Protocol 的父类是 AbstractProtocol。
1. Http11NioProtocol 构造方法
在 Connector 的构造方法中,用反射创建了一个 Http11NioProtocol 对象。
public Http11NioProtocol() {
super(new NioEndpoint());
}
public AbstractHttp11JsseProtocol(AbstractJsseEndpoint endpoint) {
super(endpoint);
}
public AbstractHttp11Protocol(AbstractEndpoint endpoint) {
super(endpoint);
setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
ConnectionHandler cHandler = new ConnectionHandler<>(this);
setHandler(cHandler);
getEndpoint().setHandler(cHandler);
}
/**
* Endpoint that provides low-level network I/O - must be matched to the
* ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO
* Endpoint etc.).
*/
private final AbstractEndpoint endpoint;
public AbstractProtocol(AbstractEndpoint endpoint) {
this.endpoint = endpoint;
setConnectionLinger(Constants.DEFAULT_CONNECTION_LINGER);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
public void setConnectionLinger(int connectionLinger) {
endpoint.setConnectionLinger(connectionLinger);
}
public void setTcpNoDelay(boolean tcpNoDelay) {
endpoint.setTcpNoDelay(tcpNoDelay);
}
在 Http11NioProtocol 构造方法里,创建了一个 NioEndpoint 对象,Http11Nio2Protocol 与 Http11NioProtocol 的区别主要在这里,Http11Nio2Protocol 在构造方法里创建的是 Nio2Endpoint 对象。这个 NioEndpoint 对象是非常重要的组件,它封装了 tomcat 的线程模型,后面会单独讲解这个类,这里先不多做描述。
在 AbstractProtocol 里把这个 NioEndpoint 对象复制给内部的 Endpoint 类型的属性,然后设置了一些 Endpoint 对象的两个属性,setConnectionLinger 和 setTcpNoDelay 方法就是调用 Endpoint 对象的相关方法。
在 Http11NioProtocol 父类的父类 AbstractHttp11Protocol 里,创建了一个 ConnectionHandler 对象,并调用 setHandler(cHandler) 把这个对象赋值给自己 handler 属性,这个属性在 AbstractHttp11Protocol 的父类 AbstractProtocol 里。
private Handler handler;
protected void setHandler(Handler handler) {
this.handler = handler;
}
ConnectionHandler 是 AbstractProtocol 类的一个静态内部类,其声明为
rotected static class ConnectionHandler implements AbstractEndpoint.Handler
可以看出 Handler 类是 AbstractEndpoint 里的一个静态的接口类型,这个 Handler 接口定义了一些处理 Socket 事件的方法。
AbstractHttp11Protocol 构造方法里接着调用了 getEndpoint().setHandler(cHandler),把 ConnectionHandler 对象赋值 NioEndpoint 的 Handler 类型的属性。
2. ProtocolHandler#init 方法
ProtocolHandler 定义了 init 和 start 方法,ProtocolHandler 的实现类 AbstractProtocol 及其子类 AbstractHttp11Protocol 实现或重载了 init 方法,AbstractHttp11Protocol 的子类都没有重载 init 方法。
2.1. AbstractHttp11Protocol#init
/**
* The upgrade protocol instances configured.
*/
private final List upgradeProtocols = new ArrayList<>();
@Override
public void init() throws Exception {
// Upgrade protocols have to be configured first since the endpoint
// init (triggered via super.init() below) uses this list to configure
// the list of ALPN protocols to advertise
for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
configureUpgradeProtocol(upgradeProtocol);
}
super.init();
}
AbstractHttp11Protocol#init 方法比较简单,就是简单调用了 configureUpgradeProtocol 方法。
/**
* The protocols that are available via internal Tomcat support for access
* via HTTP upgrade.
*/
private final Map httpUpgradeProtocols = new HashMap<>();
/**
* The protocols that are available via internal Tomcat support for access
* via ALPN negotiation.
*/
private final Map negotiatedProtocols = new HashMap<>();
private void configureUpgradeProtocol(UpgradeProtocol upgradeProtocol) {
// HTTP Upgrade
String httpUpgradeName = upgradeProtocol.getHttpUpgradeName(getEndpoint().isSSLEnabled());
boolean httpUpgradeConfigured = false;
if (httpUpgradeName != null && httpUpgradeName.length() > 0) {
httpUpgradeProtocols.put(httpUpgradeName, upgradeProtocol);
httpUpgradeConfigured = true;
getLog().info(sm.getString("abstractHttp11Protocol.httpUpgradeConfigured",
getName(), httpUpgradeName));
}
// ALPN
String alpnName = upgradeProtocol.getAlpnName();
if (alpnName != null && alpnName.length() > 0) {
if (getEndpoint().isAlpnSupported()) {
negotiatedProtocols.put(alpnName, upgradeProtocol);
getEndpoint().addNegotiatedProtocol(alpnName);
getLog().info(sm.getString("abstractHttp11Protocol.alpnConfigured",
getName(), alpnName));
} else {
if (!httpUpgradeConfigured) {
// ALPN is not supported by this connector and the upgrade
// protocol implementation does not support standard HTTP
// upgrade so there is no way available to enable support
// for this protocol.
getLog().error(sm.getString("abstractHttp11Protocol.alpnWithNoAlpn",
upgradeProtocol.getClass().getName(), alpnName, getName()));
}
}
}
}
configureUpgradeProtocol 方法也挺简单的,就是将 UpgradeProtocol 放在 httpUpgradeProtocols 和 negotiatedProtocols 里。
2.2. AbstractProtocol#init
@Override
public void init() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
logPortOffset();
}
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname, null);
}
}
if (this.domain != null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null);
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
endpoint.setDomain(domain);
endpoint.init();
}
AbstractProtocol#init 里前面几行是注册一些对象到 MBeanServer 里,最重要的是最后一行的 endpoint.init(),这一行调用了 NioEndpoint 的 init 方法。关于 NioEndpoint 的详细内容将在后续的文章里讲解。
3. ProtocolHandler#start 方法
ProtocolHandler 的实现类 AbstractProtocol 实现了 start 方法,AbstractProtocol 的子类并没有重载 start 方法。
@Override
public void start() throws Exception {
if (getLog().isInfoEnabled()) {
getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
logPortOffset();
}
endpoint.start();
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
if (!isPaused()) {
startAsyncTimeout();
}
}
}, 0, 60, TimeUnit.SECONDS);
}
在 AbstractProtocol#start 方法里先调用了 endpoint.start(),然后使用线程池来定期调用 startAsyncTimeout() 方法。
这里的 getUtilityExecutor() 返回的对象,是在 Connector 的 initInternal 中调用 ProtocolHandler#setUtilityExecutor 设值的,传入的对象 StandardServer 的 utilityExecutorWrapper,这个在之前介绍 Server 的文章里讲到过。
endpoint.start() 会在后续的文章里讲解。
protected void startAsyncTimeout() {
if (asyncTimeoutFuture == null || (asyncTimeoutFuture != null && asyncTimeoutFuture.isDone())) {
if (asyncTimeoutFuture != null && asyncTimeoutFuture.isDone()) {
// There was an error executing the scheduled task, get it and log it
try {
asyncTimeoutFuture.get();
} catch (InterruptedException | ExecutionException e) {
getLog().error(sm.getString("abstractProtocolHandler.asyncTimeoutError"), e);
}
}
asyncTimeoutFuture = getUtilityExecutor().scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
long now = System.currentTimeMillis();
for (Processor processor : waitingProcessors) {
processor.timeoutAsync(now);
}
}
}, 1, 1, TimeUnit.SECONDS);
}
}
可以看出 startAsyncTimeout 方法的作用是定期调用 waitingProcessors 里的 Processor 对象的 timeoutAsync 方法来处理一些超时的请求。
Processor 也是 tomcat 用来处理请求的一个关键组件。上文中提到的 ConnectionHandler 就是使用 Processor 来具体处理请求的。Processor 将会在后续的文章中介绍。
小结
本文介绍了 ProtocolHandler 的初始化和启动,ProtocolHandler 的默认实现类是 Http11NioProtocol。Http11NioProtocol 有一个非常重要的 NioEndpoint 对象,ProtocolHandler 的 init 和 start 方法中最关键的就是调用这个 NioEndpoint 对象的 init 和 start 方法。此外,在 AbstractHttp11Protocol 构造方法里创建了一个也是非常重要的 ConnectionHandler 对象,这个对象是用来处理请求,ConnectionHandler 使用 Processor 对象来具体处理请求。