Method URI Version(/r/n)
request headers(/r/n)
(/r/n)
request body
Version code msg(/r/n)
response headers(/r/n)
(/r/n)
response body
考虑Servlet的 service(ServletRequest req, ServletResponse res)方法,
1 Request implements ServletRequest , RequestFacade implements ServletRequest,响应同理
2 HttpRequest implements HttpServletRequest , HttpRequestFacade implements HttpServletRequest,响应同理
3 Request
读取输入流
解析请求行(uri存在查询字符串需要提出,jsessionid若存在需提出)
解析请求头:特殊处理:content-length, content-type, cookie(需单独解析cookie)
参数解析:查询字符串中提取,post且content-length大于0且content-type为application/x-www-form-urlencoded时需从请求体提取
根据uri判断是访问静态资源还是servlet(调用不同processor),访问servlet需要生成类加载器加载servlet类(类名uri传入),调用service方法。
StringManager:错误码- 错误描述的映射
1 按package区分,每个package一个StringManager,每个package下多个资源文件,StringManager读取哪一个呢?根据服务器语言环境来判别。
2 输入错误码获取错误描述
http 1.1
默认持久连接
分块编码 (响应头Transfer-Encoding: chunked),一般我们用content-length来界定请求或响应边界,但假设我们需要动态生成内容,很难获取内容长度,按照如下格式发送,浏览器就可以在无content-length下识别数据边界了。
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
1C\r\n
and this is the second one\r\n
8\r\n
sequence\r\n
0\r\n
\r\n
状态码100:请求头Expect:100-continue 客户端表明自己要发很长的请求体(Post大量数据),服务端若可以接收返回响应行HTTP/1.1 100 Continue
考虑Connector, (运行在自己的线程,implements Runnable, 不断accept,负责创建且含processor池(stack),取一processor并将socket安全发布给processor所运行线程,继续下一次accept)
processor(运行在自己的线程,循环:1获取socket,2处理socket,3将自己放回processor池),通过wait notifyAll 获取到socket,(可通过Processor对象的成员变量保存一个socket,然后processor run方法内局部变量指向正在处理的socket)
connector 线程阻塞:Processor对象的成员变量已有socket,需等待processor处理完当前socket并从成员变量取走socket才可唤醒
processor 线程阻塞:当前无正在处理的socket,成员变量内也无socket,需connector给成员变量设置一socket才可唤醒
processor 的设置需从connector(暴露给用户设置)获取
processor 解析完毕后交给connector.getContainer().invoke(request,response);
processor 解析:
boolean keepAlive = true;
while (!stopped && ok && keepAlive) { // processor实例未被终止,上次解析未出现错误
// 获取输入流,构建和解析请求和响应
// connector.getContainer().invoke(request,response);
keepAlive = false; // 1 http1.0 且请求头未带来Connection: keep-alive 2 http1.1 但是请求头Connection:close
// 3响应头Connection:close
}
socket.close();
Valve:first,basic(最后一个,一般是StandardXXXValve,多用于调度下一级容器(一般从请求中获取)来处理)
StandardWrapperValve : 获取Wrapper,wrapper.allocate()获取Servlet,调用Servlet的service方法
Lifecycle( 定义各种eventType ,增加,移除LifecycleListener方法,init,start,stop,destroy方法, 获取LifecycleState方法 )
LifecycleListener (唯一方法lifecycleEvent(LifecycleEvent event);)
LifecycleEvent(含Lifecycle, eventType, data)
LifecycleState (enum,含available和eventType ,如STARTED状态available为true,eventType为AFTER_START_EVENT)
LifecycleBase (Lifecycle的实现,含LifecycleSupport(将Listener相关方法委托给它 ),实现生命周期方法如init等,其子类需实现initInternal等方法,如start内STARTING_PREP,子类startInternal内STARTING,start内STARTED )
LifecycleSupport(含Listener数组,负责Listener相关方法最终调用)
Container扩展自Lifecycle,关联了Logger,Loader,Manager,Cluster,Pipeline,Realm
Loader:
Context关联的WebappLoader和WebappClassLoader
为什么Loader,不直接使用AppClassLoader(环境变量CLASSPATH)?1 保护作用,避免servlet能访问所有类,如Catalina ClassLoader所加载类(/server)servlet就不能访问。2 支持自动重载,线程不断检查时间戳看WEB-INF/classes或WEB-INF/lib是否改变
Loader: classLoader,container, delegete (boolean, 是否优先委托给父类载入器),
reloadable:设置container时会用context的reloadable设置,当然也可以直接设置
repository,:loader启动时创建classloader实例并将WEB-INF/classes和WEB-INF/lib设置入classloader,当然也可以手动将其他repository设置入classloader
modified(委托给classloader的modified方法,检查并返回是否改变)
public void backgroundProcess() { // loader 的后台处理方法
if (reloadable && modified()) {
try {
Thread.currentThread().setContextClassLoader
(WebappLoader.class.getClassLoader());
if (container instanceof StandardContext) {
((StandardContext) container).reload(); // reload 先调用stop后start
}
} finally {
if (container.getLoader() != null) {
Thread.currentThread().setContextClassLoader
(container.getLoader().getClassLoader());
}
}
} else {
closeJARs(false);
}
}
WebappClassLoader:重写了loadClass
1 查找本地缓存,根据类名查找ResourceEntry(含Class)的Map
2 查找ClassLoader自带缓存,native方法
3 用j2seClassLoader(bootstrap,但问题是bootstrap多为null,那么用extClassLoader)进行加载,防止类似Object类被自定义加载器加载了
4 确定是否代理(delegete或类的包名以packageTriggers之一开头,即某些类一定要先双亲委托给parent加载)
5 若代理(双亲委托),先代理给parent,不行再自己加载,若非代理,先自己加载(findClass),不行再代理给parent
container start时可能会开启后台线程,取决于backgroundProcessorDelay(<=0, 不开启由父容器的线程执行自己的后台方法,>0开启)
后台线程根据backgroundProcessorDelay周期执行自己的或未开启后台线程(<=0)的children的backgroundProcess方法
backgroundProcess: 一般实现为执行cluster,loader,manager,realm,各valve的backgroundProcess方法
实际情况:一般只有engine有后台线程(代码里设置了backgroundProcessorDelay为10),engine下所有子容器都不开(代码未设置,默认-1,但你可以server.xml 配置)
Manager:Session管理器,与Context关联
用户通过request获取session,
Session接口:tomcat内session的功能,含getSession方法返回HttpSession,expire方法
HttpSession:api规范表明了提供到用户的功能
StandardSession:实现Session,HttpSession接口,返回StandardSessionFacade给用户,
Manager:backgroundProcess调用每个session的isValid方法(内部判断是否过期,过期则内部调用expire方法)
StandardManager: 关闭时(stopInternal调用unload) 将session写入文件SESSIONS.ser,启动时(load)从文件中读取session
PersistentManagerBase:实现DistributedManager接口,含Store,提供换出(内存中太多了或空闲超过了一定时间,放入Store,内存不放)和备份(session空闲时间超过了一定时间,内存中和Store中都保存),换出和备份都在backgroundProcess中调用,换入在用户查找session或Manager启动时进行。
PersistentManager: 基本上就是继承PersistentManagerBase
Store接口:最重要save(session),load(id)
StoreBase:PersistentManagerBase的backgroundProcess会调用StoreBase的processExpires(查看Store内的session是否过期,并清除过期session,此session若内存中也有也需清除)
FileStore: 每个session用sessionid.session文件存放,目录可自定,session所含所有对象都需实现Serializable接口(jdk序列化,深复制)
JDBCStore
Realm: authenticate方法 ,一般和Context对象关联,身份验证,tomcat-user.xml
RealmBase:
MemoryRealm:默认实现,第一次调用读取tomcat-user.xml,内含 用户名-Principal的map
JDBCRealm ,JNDIRealm ,UserDatabaseRealm
Principal
GenericPrincipal:name,password,roles
LoginConfig:authMethod(basic,digest,form,client-cert), errorPage(form才有,String), loginPage(form才有,String), realmName,web.xml含login-config元素才创建此对象放入Context
Authenticator:authenticate方法
AuthenticatorBase: 继承ValveBase,因此我们所用的Authenticator都是valve
BasicAuthenticator
FormAuthenticator
DigestAuthenticator
SSLAuthenticator(对应client-cert)
NonLoginAuthenticator:auth-method没有设置
context的start方法发出configure_start事件,ContextConfig(listener)调用authenticatorConfig方法根据LoginConfig的authMethod(来自于login-config元素内的auth-method元素)生成相应的Authenticator加入Pipeline(org/apache/catalina/startup/Authenticators.properties存放authMethod和其相应的Authenticator的className)
request到达Context的Authenticator类型Valve,调用Valve的invoke方法,调用Authenticator的authenticate方法,调用Realm的authenticate方法,不同Authenticator可能用户名密码的获取方式,处理失败的方式不一样。