Tomcat 配置

Tomcat 功能说明与配置说明.

理论基础

Tomcat服务安装启动

  • 1.官网下载JDK与Tomcat直接解压,JDK7对应Tomcat7,JDK8对应Tomcat8

配置

JAVA_HOME配置

windows setclasspath.bat文件最前面加上:

1
2
set JAVA_HOME=D:\java\jdk1.8.0_171
set JRE_HOME=D:\java\jdk1.8.0_171\jre

linux setclasspath.sh文件最前面加上:

1
2
JAVA_HOME="/usr/local/java/jdk1.8.0_171"
JRE_HOME="/usr/local/java/jdk1.8.0_171/jre"

Tomcat 7.0.100.0 Log4j配置,对tomcat大日志进行切割

  1. $CATALINA_BASE/lib目录创建log4j.properties文件注意里面语法与包路径不同版本会有所区别具体参考官方文档,添加log4j.jar必须v1.2.x版本
  2. 去官网下载扩展组件Extras:tomcat-juli.jar和tomcat-juli-adapters.jar到lib目录,复制tomcat-juli.jar到bin目录
  3. 删除logging.properties
  4. 启动tomcat

tomcat7 log4j.properties配置例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
log4j.rootLogger=INFO, CATALINA

# Define all the appenders
log4j.appender.CATALINA=org.apache.log4j.extend.DayRollingFileAppender
log4j.appender.CATALINA.File=${catalina.base}/logs/catalina
log4j.appender.CATALINA.Append=true
log4j.appender.CATALINA.Encoding=UTF-8
# Roll-over the log once per day
log4j.appender.CATALINA.layout = org.apache.log4j.PatternLayout
log4j.appender.CATALINA.MaxBackupIndex=30
log4j.appender.CATALINA.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.LOCALHOST=org.apache.log4j.extend.DayRollingFileAppender
log4j.appender.LOCALHOST.File=${catalina.base}/logs/localhost
log4j.appender.LOCALHOST.Append=true
log4j.appender.LOCALHOST.Encoding=UTF-8
log4j.appender.LOCALHOST.layout = org.apache.log4j.PatternLayout
log4j.appender.LOCALHOST.MaxBackupIndex=30
log4j.appender.LOCALHOST.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.MANAGER=org.apache.log4j.extend.DayRollingFileAppender
log4j.appender.MANAGER.File=${catalina.base}/logs/manager
log4j.appender.MANAGER.Append=true
log4j.appender.MANAGER.Encoding=UTF-8
log4j.appender.MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.MANAGER.MaxBackupIndex=30
log4j.appender.MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.HOST-MANAGER=org.apache.log4j.extend.DayRollingFileAppender
log4j.appender.HOST-MANAGER.File=${catalina.base}/logs/host-manager
log4j.appender.HOST-MANAGER.Append=true
log4j.appender.HOST-MANAGER.Encoding=UTF-8
log4j.appender.HOST-MANAGER.layout = org.apache.log4j.PatternLayout
log4j.appender.HOST-MANAGER.MaxBackupIndex=30
log4j.appender.HOST-MANAGER.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Encoding=UTF-8
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = %d [%t] %-5p %c- %m%n

# Configure which loggers log to which appenders
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost]=INFO, LOCALHOST
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager]=\
INFO, MANAGER
log4j.logger.org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager]=\

还有其他分隔方案比如:

  1. 创建shell脚本进行catalina.out日志文件切割,使用linux自带定时器。(执行脚本的定时任务的频率以及时间都要控制好,不然可能会有部分日志内容保存不下来的情况)
  2. 用cronolog软件来分割Tomcat的catalina.out文件
  3. 使用logrotate软件来切割日志文件 cat /var/lib/logrotate/logrotate.status查看切割状态,logrotate有两个条件,一个是保存多少天日志,还有一个是size超过多大才进行分割

配置资源gzip压缩

1
2
3
4
5
6
7
<Connector port="8090" protocol="HTTP/1.1"
connectionTimeout="9000" maxThreads="1000" acceptCount="2000"
redirectPort="8443" maxHttpHeaderSize="8192"
compression="on"
compressionMinSize="1024"
noCompressionUserAgents="gozilla,traviata"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,text/json,application/css,application/javascript,application/json"/>

compression=”on” 开启压缩。可选值:”on”开启,”off”关闭,”force”任何情况都开启
compressionMinSize=”2048”大于2KB的文件才进行压缩,默认2048B。如果配置不合理,产生的后果是小文件压缩后反而变大了,达不到预想的效果
noCompressionUserAgents=”gozilla, traviata”,对于这两种浏览器,不进行压缩
compressableMimeType=”text/html,text/xml,application/javascript,text/css,text/plain,text/json”会被压缩的MIME类型列表,多个逗号隔,表明支持html、xml、js、css、json等文件格式的压缩

Connector 参数说明

  • port 指定端口accept客户端的链接
  • protocol “HTTP/1.1”是默认值,等效于”org.apache.coyote.http11.Http11Protocol”,在Tomcat 6.0之后,还提供了NIO的方式,可以有效的提升性能,特别是在大量长连接/数据上传+下载等web应用中.此时portocal=”org.apache.coyote.http11.Http11NioProtocol”
    BIO:JDK 1.5+,tomcat 5.x+
    NIO:JDK 1.6+,tomcat 6.x+
    NIO2:JDK 1.7+,tomcat 7.x+
  • maxParameterCount http-get请求中允许传递的查询字符串的最大个数,”-1”表示无限制,为了安全和规范,maxHeaderCount和maxParamterCount通常应该合理,建议设置为100等;如果请求参数再多,那么就建议使用post body发送或者拆分请求
  • maxPostSize http-post请求中数据(body)的最大尺寸,单位:byte,默认值为2M
  • URIEncoding http-get请求中,使用何种字符集对查询字符串进行编码,默认为”iso-8859-1”
  • useBodyEncodingForURI 是否使用”Content-type”中指定的编码方式对http-get请求中查询字符串进行编码.如果为”true”,将会忽略”URIEncoding”配置项,转而使用header中”content-Type”指定的编码方式
  • maxThreads 用于接收和处理client端请求的最大线程数,默认200,太大的值,并不能提升NIO性能,反而会使性能下降,因为线程切换(CS)将会占据CPU的大量时间
  • compression 是否对http相应数据启用Gzip压缩
  • acceptCount 当tomcat请求处理线程池中的所有线程都处于忙碌状态时,此时新建的链接将会被放入到pending队列,acceptCount即是此队列的容量,如果队列已满,此后所有的建立链接的请求(accept),都将被拒绝。默认为100。在高并发/短链接较多的环境中,可以适当增大此值;当长链接较多的场景中,可以将此值设置为0.
  • address 当物理server上绑定了多个IP地址时,可以通过“address”来指定tomcat-server需要bind的地址.默认将port关联到所有的ip上
  • bufferSize 链接在读取stream时,buffer数据的尺寸
  • connectionLinger socket linger参数值。当socket即将关闭时(前)阻塞的时间,单位:秒。如果设置为-1,表示关闭linger。在BIO(Blocking IO)和AJP链接中默认为100,NIO中默认为25
  • keepAliveTimeout 当无实际数据交互时,链接被保持的时间,单位:毫秒。在未指定此属性时,将使用connectionTimeout作为keepAliveTimeout。对于HTTP请求,server端为了支撑较高的吞吐量,不可能无限制的keepAlive一个链接(在设计要求上,这个和TCP通讯有本质的区别),keepAliveTimeout超时后,将会导致链接关闭。如果此tomcat设计为“长链接”服务,可以适当增加keepAliveTimeout值,否则无需设置此值。不过我们通常在tomcat上层还有nginx等代理服务器,我们通常希望链接keepAlive的机制由代理服务器控制,比如nginx来决定链接是否需要“保持活性”(注意,与keep_alive不同),当然nginx服务器只会保留极少的长连接,几乎所有的链接都会在使用结束后主动close;因为链接复用层,将有nginx与client保持,而不再是tomcat与client保持。太多的keepAlive链接,尽管提高了链接使用效率,但是对负载均衡不利。
  • maxKeepAliveRequests tomcat需要保持的最大请求数,即处于keepAlive状态的请求的个数,建议此值为maxThreads * 0.5,不得大于maxThreads,否则将得不到预期的效果。-1表示不限制,1表示关闭keepAlive机制。
  • maxConnections tomcat允许接收和处理的最大链接数,对于BIO而言此值默认与maxThreads参数一样,对于NIO而言此值默认为10000。
  • acceptorThreadCount 默认为1,表示用于accept新链接的线程个数,如果在多核CPU架构下,此值可以设置为2,官方不建议设定超过2个的值。
  • maxHttpHeaderSize http头的最大尺寸,默认为8192,单位为“字节”。
  • minSpareThreads 线程池中,保持活跃的线程的最小数量,默认为10
  • SSLEnabled 是否开启ssl支持,默认为false;通常SSL应该在nginx等代理层,我们不应该让tomcat直接接入
  • pollerThreadCount 表示用于polling IO事件的线程个数,默认为1。NIO
  • useSendfile 是否开启sendfile特性,默认为true。对于web应用而言,通常project中还会包含一定数量的静态资源,比如图片、CSS、js、html等,sendfile在一定程度上可以提高性能。NIO
  • selectorTimeout 选择器阻塞的时间,如果你进行过NIO开发,应该知道此参数的含义,默认值为1000毫秒。此值不要太大,因为selector线程本身还需要用来清理已关闭的链接等。NIO
  • selectorPool.maxSelectors:NIO中选择的个数,默认值为200。NIO

内存大小配置

整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小

修改tomcat下catalina.sh里,位置cygwin=false

1
2
3
# OS specific support.  $var _must_ be set to either true or false.
JAVA_OPTS="-server -Xms4096M -Xmx4096M -XX:NewSize=1024M -XX:MaxNewSize=2048M -Xss1024K -XX:PermSize=128m -XX:MaxPermSize=256m"
cygwin=false
  • -Xms 初始堆内存大小,一般设置为和Xmx一致,避免每次垃圾回收后重新分配内存
  • -Xmx 堆最大可用内存
  • -XX:NewSize 初始新生代大小
  • -XX:MaxNewSize 最大新生代大小
  • -Xss 设置每个线程栈的大小
  • -XX:PermSize JVM初始非堆内存、持久代、物理内存的1/64
  • -XX:MaxPermSize JVM最大非堆内存、持久代、物理内存的1/4
  • -XX:NewRatio 新生代与老年代比例,默认1:2也就是1/3新生代,
  • -XX:SurvivorRatio=8 默认的,Eden : from : to = 8 : 1 : 1, Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小,JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的
  • -XX:MaxTenuringThreshold=15一个对象每次Minor Gc时,活着的对象都会在s0和s1区域转移,经过经过Minor GC 15次后会进入Old区域
  • -XX:MetaspaceSize=512M XX:MaxMetaspaceSize=1024M JAVA8 替代永久代的元空间
  • -XX:+UseSerialGC 设置串行收集器
  • -XX:+UseParallelGC 设置并行收集器
  • -XX:+UseParalledlOldGC 设置并行年老代收集器
  • -XX:+UseConcMarkSweepGC 设置并发收集器
  • -XX:ParallelGCThreads=n 设置并行收集器收集时使用的CPU数。并行收集线程数。
  • -XX:MaxGCPauseMillis=n 设置并行收集最大暂停时间
  • -XX:GCTimeRatio=n 设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
  • -XX:+CMSIncrementalMode 设置为增量模式。适用于单CPU情况。
  • -XX:ParallelGCThreads=n 设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
  • -XX:+PrintGC 统计信息

在发生FULL GC的时候,意味着JVM会安全的暂停所有正在执行的线程(Stop The World),来回收内存空间,在这个时间段内,所有除了回收垃圾的线程外,其他有关JAVA的程序,代码都会静止,反映到系统上,就会出现系统响应大幅度变慢,卡机等状态。

Java8永久代被移除,增加元空间。

垃圾收集:引用计数、可达性分析、标记清除法、标记整理法、复制算法。串行收集器、并行收集器、并发收集器

查看JVM内存使用情况:

1
2
3
4
5
6
7
8
9
ps -ef | grep tomcat # 查看进程号
jmap -heap 34840 # 查看内存情况
jstat -gc 34840 5000 # jstat [Options] id [interval] [count]
pmap 34840 # 最后显示的是虚拟内存大小 单位k
jmap -histo 34840 # 单位字节
cat /proc/34840/smaps
ps aux| sort -k4nr | head -n 10
top -p 34840 # 可以查看CPU 内存使用率
cat /proc/34840/status # 查看详细状态,VmRSS对应的值就是物理内存占用(虚拟内存驻留集合大小,驻留内存的部分,包括代码,数据,栈),VmSize虚拟内存大小,VmLck虚拟内存锁,VmData虚拟内存数据 堆使用的虚拟内存,VmStk虚拟内存栈 栈使用的虚拟内存

清除缓存

删除tomcat/work 目录下面的文件与文件夹

redhat6开机自启动

  • cd /etc/init.d
  • vi tomcat8-xxx-spi 拿catalina.sh改也行,加上前面9行。不想显示tomcat8还可以根据自己需求修改成其他名字
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #!/bin/bash
    # description: Tomcat Start Stop Restart
    # processname: tomcat
    # chkconfig: 234 20 80
    JAVA_HOME=/usr/java/jdk1.8.0_131
    export JAVA_HOME
    PATH=$JAVA_HOME/bin:$PATH
    export PATH
    CATALINA_HOME=/home/infotech/project/xxx-spi-xxx-8888

    case $1 in
    start)
    sh $CATALINA_HOME/bin/startup.sh
    ;;
    stop)
    sh $CATALINA_HOME/bin/shutdown.sh
    ;;
    restart)
    sh $CATALINA_HOME/bin/shutdown.sh
    sh $CATALINA_HOME/bin/startup.sh
    ;;
    esac
    exit 0
  • chmod 755 tomcat8-xxx-spi
  • chkconfig --add tomcat8-xxx-spi
  • chkconfig --level 234 tomcat8-xxx-spi on
  • chkconfig --list tomcat8-xxx-spi
  • service tomcat8-xxx-spi start 通过命令启动服务
  • service tomcat8-xxx-spi stop 通过命令关闭服务

centos7开机自启动

  • 系统(/user/lib/systemd/system/)、用户(/etc/lib/systemd/user/)
  • vi /usr/lib/systemd/system/tomcat-test.service 新建一个tomcat-test服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    [Unit]
    Description=tomcat
    After=syslog.target network.target # 在指定服务后运行

    [Service]
    #User=infotech # 运行用户 一般都需要指定用户,可以不配置Type使用默认
    Type=forking # 后台运行,simple(默认值)

    #PIDFile=/home/infotech/project/tomcat-test-8888/tomcat-test.pid # 开启后必须在catalina.sh加入CATALINA_PID,这个非常有必要
    #Environment="JAVA_HOME=/usr/java/jdk1.8.0_131" # 设置环境变量

    WorkingDirectory=/home/infotech/project/tomcat-test-8888/
    ExecStart=/home/infotech/project/tomcat-test-8888/bin/startup.sh # 脚本中的命令都需要使用全路径
    ExecStop=/home/infotech/project/tomcat-test-8888/bin/shutdown.sh
    ExecReload=/bin/kill -s HUP $MAINPID
    RemainAfterExit=yes

    [Install]
    WantedBy=multi-user.target # 多用户模式启动
  • 如果设置了PIDFile需要在tomcat bin/catalina.sh中加入CATALINA_PID参数
    1
    2
    3
    4
    5
    6
    CATALINA_PID=/home/infotech/project/tomcat-test-8888/tomcat-test.pid

    # OS specific support. $var _must_ be set to either true or false.

    cygwin=false
    ...
  • systemctl daemon-reload 使服务生效
  • systemctl enable tomcat-test.service 设置为开机启动
  • systemctl start tomcat-test.service 启动服务
  • systemctl status tomcat-test.service 监测服务状态
  • systemctl list-units --type=service 显示已启动服务

虽然可以自启而且可以保障通过systemctl命令只启动一个tomcat,但是如果使用tomcat/bin/startup.sh来启动tomcat还是会导致启动多个程序,所以得设置pid来避免重复启动,或者关闭脚本改用kill -9 xxx 命令实现或者killall java来实现会关闭所有tomcat程序。systemctl与直接用脚本启动的程序是独立的,需要用pid来约束。配置参数等等都得正确,否则直接调用脚本启动呢的tomcat,调用tomcat自带的关闭脚本无法关闭新启动的进程,导致出现两个进程。某些情况出现tomcat无法通过shutdown.sh正常关闭需要用kill实现,之前出现过netty程序启动着无法关闭的情况,当然应该还是有办法通过优化程序避免这种问题的,最直接的还是用kill -9实现。简单点就算统一使用systemctl启动关闭tomcat程序这样,也是不会出现多个程序的情况的,直接调用startup脚本会不受控制。

tomcat-test.service增加PIDFile=/home/infotech/project/tomcat-test-8888/bin/tomcat-test.pid,增加PID还是非常有必要的。

安全加固

禁用TRACE

修改tomcat/server.conf文件,增加allowTrace="true"。加完使用postman测试下

1
2
3
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" allowTrace="true"/>

如下方法在tomcat/conf/web.xml最后加上一个节点,除了TRACE方法不生效,其他方法可以生效。测试发现在linux下还是不生效

1
2
3
4
5
6
7
8
9
10
11
<security-constraint>
<web-resource-collection>
<url-pattern>/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>HEAD</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
</web-resource-collection>
<auth-constraint></auth-constraint>
</security-constraint>

在代码中过滤TRACE请求

1
2
3
4
5
6
7
// 比如在请求过滤器RequestFilter中增加,只允许让POST、GET,或者针对性过滤
String method = request.getMethod();
if (!(method.equals(HttpMethod.POST) || method.equals(HttpMethod.GET))) { // 或者method.equals("TRACE")
log.warn("警告:禁止POST和GET以外的请求方法");
response.setStatus(405);
return;
}

CSRF

CSRF Prevention Filter

安全漏洞

禁用RC4、DES,CVE-2015-2808,CVE-2016-2183
修改tomcat/conf/server.xml配置文件,找到https端口配置

1
2
3
4
5
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
sslEnabledProtocols="TLSv1,TLSv1.1,TLSv1.2"
ciphers="TLS_ECDHE_RSA_WITAES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA"/>

RC4与DES/AES一样是一种对称加密算法。TLS_RSA_WITH_AES_128_CBC_SHA256:TLS–SSL还是TSL,RSA–所用非对称加密算法,AES–所用对称加密算法,128–对称加密分组长度,CBC–分组加密模式,SHA256–所用完整性验证算法

检测到目标主机可能存在缓慢的HTTP拒绝服务攻击
修改超时时间,通过SlowHTTPTest工具测试

1
2
3
4
5
6
7
8
9
10
11
12
# 测试命令
./slowhttptest -c 1000 -H -g -o slowhttp -i 10 -r 200 -t GET -u http://192.168.1.111:8080/abc -x 24 -p 3

# 查看连接情况
netstat -tnl
ss -int # ss -int | grep 8080
ss -inlt
ss -t state established
netstat -an|grep 8080 # 查看连接详情
netstat -nat | grep -i "8080" | wc -l # 统计连接数
netstat -na | grep ESTABLISHED | wc -l # 总连接数,测试的时候会有1000个ESTABLISHED,如果在tomcat中设置超时时间ESTABLISHED会随着时间到而下降变成TIME_WAITE,当 TCP 连接主动关闭时,都会经过 TIME_WAIT 状态。
netstat -ae| grep "TIME_WAIT" | wc -l # 查看TIME_WAIT状态的个数
1
2
3
4
5
6
7
8
# tomcat 相关配置项目
maxThreads — The maximum number of request processing threads that a given connector creates
acceptCount — The maximum queue length for incoming connection requests to the given connector when all possible request processing threads are in use.
maxConnections # The maximum number of connections that the server will accept and process at any given time. For NIO and NIO2 the default is 10000. For APR/native, the default is 8192. 限制后新连接无法建立进来
keepAliveTimeout
maxKeepAliveRequests
maxHttpHeaderSize
connectionTimeout="9000" # 解决方案就是超时时间减少提前释放资源

其他

强制关闭

1 通过命令

通过ps -ef|grep tomcat 查看进程号 通过kill -9 pid关闭

2 通过配置

  1. catalina.sh开头第二行加入CATALINA_PID="tomcat.pid"或者CATALINA_PID="/var/run/tomcat.pid"pid文件存放目录,不推荐用该方法
  2. ./catalina.sh -help查看具体参数
  3. 修改catalina.sh,推荐使用,重复启动会提示Existing PID file found during start。启动关闭的时候会创建pid与删除pid文件
    1
    2
    3
    4
    5
    PRGDIR=`dirname "$PRG"` # 后面加入下面语句
    if [ -z "$CATALINA_PID" ]; then
    CATALINA_PID=$PRGDIR/CATALINA_PID
    cat $CATALINA_PID
    fi
  4. shutdown.sh 3 -force 关闭,或者./catalina.sh stop -force

一条命令或写成脚本

ps -elf | grep tomcat | grep -v grep | awk '{print $4}' | xargs kill -9
awk '{print $4}' 每行按空格或TAB分割,输出文本中的第4项
xargs 给命令传递参数

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
/**
* This class is the equivalent of the Worker, but will simply use in an
* external Executor thread pool.
*/
protected class SocketProcessor implements Runnable {

protected NioChannel socket = null;
protected SocketStatus status = null;

public SocketProcessor(NioChannel socket, SocketStatus status) {
reset(socket,status);
}

public void reset(NioChannel socket, SocketStatus status) {
this.socket = socket;
this.status = status;
}

@Override
public void run() {
SelectionKey key = socket.getIOChannel().keyFor(
socket.getPoller().getSelector());
KeyAttachment ka = null;

if (key != null) {
ka = (KeyAttachment)key.attachment();
}

// Upgraded connections need to allow multiple threads to access the
// connection at the same time to enable blocking IO to be used when
// NIO has been configured
if (ka != null && ka.isUpgraded() &&
SocketStatus.OPEN_WRITE == status) {
synchronized (ka.getWriteThreadLock()) {
doRun(key, ka);
}
} else {
synchronized (socket) {
doRun(key, ka);
}
}
}

private void doRun(SelectionKey key, KeyAttachment ka) {
try {
int handshake = -1;

try {
if (key != null) {
// For STOP there is no point trying to handshake as the
// Poller has been stopped.
if (socket.isHandshakeComplete() ||
status == SocketStatus.STOP) {
handshake = 0;
} else {
handshake = socket.handshake(
key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
status = SocketStatus.OPEN_READ;
}
}
}catch ( IOException x ) {
handshake = -1;
if ( log.isDebugEnabled() ) log.debug("Error during SSL handshake",x);
}catch ( CancelledKeyException ckx ) {
handshake = -1;
}
if ( handshake == 0 ) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (status == null) {
state = handler.process(ka, SocketStatus.OPEN_READ);
} else {
state = handler.process(ka, status);
}
if (state == SocketState.CLOSED) {
// Close socket and pool
close(ka, socket, key, SocketStatus.ERROR);
}
} else if (handshake == -1 ) {
close(ka, socket, key, SocketStatus.DISCONNECT);
} else {
ka.getPoller().add(socket, handshake);
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key, null, false);
} catch (OutOfMemoryError oom) {
try {
oomParachuteData = null;
log.error("", oom);
if (socket != null) {
socket.getPoller().cancelledKey(key,SocketStatus.ERROR, false);
}
releaseCaches();
}catch ( Throwable oomt ) {
try {
System.err.println(oomParachuteMsg);
oomt.printStackTrace();
}catch (Throwable letsHopeWeDontGetHere){
ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
}
}
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
}catch ( Throwable t ) {
log.error("",t);
if (socket != null) {
socket.getPoller().cancelledKey(key,SocketStatus.ERROR,false);
}
} finally {
socket = null;
status = null;
//return to cache
if (running && !paused) {
processorCache.offer(this);
}
}
}

private void close(KeyAttachment ka, NioChannel socket, SelectionKey key,
SocketStatus socketStatus) {
try {
if (ka != null) {
ka.setComet(false);
}
if (socket.getPoller().cancelledKey(key, socketStatus, false) != null) {
// SocketWrapper (attachment) was removed from the
// key - recycle both. This can only happen once
// per attempted closure so it is used to determine
// whether or not to return socket and ka to
// their respective caches. We do NOT want to do
// this more than once - see BZ 57340 / 57943.
if (running && !paused) {
nioChannels.offer(socket);
}
if (running && !paused && ka != null) {
keyCache.offer(ka);
}
}
} catch ( Exception x ) {
log.error("",x);
}
}
}
public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
/**
* Process pipelined HTTP requests using the specified input and output
* streams.
*
* @param socketWrapper Socket from which the HTTP requests will be read
* and the HTTP responses will be written.
*
* @throws IOException error during an I/O operation
*/
@Override
public SocketState process(SocketWrapper<S> socketWrapper)
throws IOException {
RequestInfo rp = request.getRequestProcessor();
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

// Setting up the I/O
setSocketWrapper(socketWrapper);
getInputBuffer().init(socketWrapper, endpoint);
getOutputBuffer().init(socketWrapper, endpoint);

// Flags
keepAlive = true;
comet = false;
openSocket = false;
sendfileInProgress = false;
readComplete = true;
if (endpoint.getUsePolling()) {
keptAlive = false;
} else {
keptAlive = socketWrapper.isKeptAlive();
}

if (disableKeepAlive()) {
socketWrapper.setKeepAliveLeft(0);
}

while (!getErrorState().isError() && keepAlive && !comet && !isAsync() &&
upgradeInbound == null &&
httpUpgradeHandler == null && !endpoint.isPaused()) {

// Parsing the request header
try {
setRequestLineReadTimeout();

if (!getInputBuffer().parseRequestLine(keptAlive)) {
if (handleIncompleteRequestLineRead()) {
break;
}
}

// Process the Protocol component of the request line
// Need to know if this is an HTTP 0.9 request before trying to
// parse headers.
prepareRequestProtocol();

if (endpoint.isPaused()) {
// 503 - Service unavailable
response.setStatus(503);
setErrorState(ErrorState.CLOSE_CLEAN, null);
} else {
keptAlive = true;
// Set this every time in case limit has been changed via JMX
request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
request.getCookies().setLimit(getMaxCookieCount());
// Currently only NIO will ever return false here
// Don't parse headers for HTTP/0.9
if (!http09 && !getInputBuffer().parseHeaders()) {
// We've read part of the request, don't recycle it
// instead associate it with the socket
openSocket = true;
readComplete = false;
break;
}
if (!disableUploadTimeout) {
setSocketTimeout(connectionUploadTimeout);
}
}
} catch (IOException e) {
if (getLog().isDebugEnabled()) {
getLog().debug(
sm.getString("http11processor.header.parse"), e);
}
setErrorState(ErrorState.CLOSE_NOW, e);
break;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
UserDataHelper.Mode logMode = userDataHelper.getNextMode();
if (logMode != null) {
String message = sm.getString(
"http11processor.header.parse");
switch (logMode) {
case INFO_THEN_DEBUG:
message += sm.getString(
"http11processor.fallToDebug");
//$FALL-THROUGH$
case INFO:
getLog().info(message, t);
break;
case DEBUG:
getLog().debug(message, t);
}
}
// 400 - Bad Request
response.setStatus(400);
setErrorState(ErrorState.CLOSE_CLEAN, t);
getAdapter().log(request, response, 0);
}

if (!getErrorState().isError()) {
// Setting up filters, and parse some request headers
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
try {
prepareRequest();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (getLog().isDebugEnabled()) {
getLog().debug(sm.getString(
"http11processor.request.prepare"), t);
}
// 500 - Internal Server Error
response.setStatus(500);
setErrorState(ErrorState.CLOSE_CLEAN, t);
getAdapter().log(request, response, 0);
}
}

if (maxKeepAliveRequests == 1) {
keepAlive = false;
} else if (maxKeepAliveRequests > 0 &&
socketWrapper.decrementKeepAlive() <= 0) {
keepAlive = false;
}

// Process the request in the adapter
if (!getErrorState().isError()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
adapter.service(request, response);
// Handle when the response was committed before a serious
// error occurred. Throwing a ServletException should both
// set the status to 500 and set the errorException.
// If we fail here, then the response is likely already
// committed, so we can't try and set headers.
if(keepAlive && !getErrorState().isError() && (
response.getErrorException() != null ||
(!isAsync() &&
statusDropsConnection(response.getStatus())))) {
setErrorState(ErrorState.CLOSE_CLEAN, null);
}
setCometTimeouts(socketWrapper);
} catch (InterruptedIOException e) {
setErrorState(ErrorState.CLOSE_NOW, e);
} catch (HeadersTooLargeException e) {
getLog().error(sm.getString("http11processor.request.process"), e);
// The response should not have been committed but check it
// anyway to be safe
if (response.isCommitted()) {
setErrorState(ErrorState.CLOSE_NOW, e);
} else {
response.reset();
response.setStatus(500);
setErrorState(ErrorState.CLOSE_CLEAN, e);
response.setHeader("Connection", "close"); // TODO: Remove
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
getLog().error(sm.getString("http11processor.request.process"), t);
// 500 - Internal Server Error
response.setStatus(500);
setErrorState(ErrorState.CLOSE_CLEAN, t);
getAdapter().log(request, response, 0);
}
}

// Finish the handling of the request
rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);

if (!isAsync() && !comet) {
if (getErrorState().isError()) {
// If we know we are closing the connection, don't drain
// input. This way uploading a 100GB file doesn't tie up the
// thread if the servlet has rejected it.
getInputBuffer().setSwallowInput(false);
} else {
// Need to check this again here in case the response was
// committed before the error that requires the connection
// to be closed occurred.
checkExpectationAndResponseStatus();
}
endRequest();
}

rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);

// If there was an error, make sure the request is counted as
// and error, and update the statistics counter
if (getErrorState().isError()) {
response.setStatus(500);
}
request.updateCounters();

if (!isAsync() && !comet || getErrorState().isError()) {
if (getErrorState().isIoAllowed()) {
getInputBuffer().nextRequest();
getOutputBuffer().nextRequest();
}
}

if (!disableUploadTimeout) {
if(endpoint.getSoTimeout() > 0) {
setSocketTimeout(endpoint.getSoTimeout());
} else {
setSocketTimeout(0);
}
}

rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);

if (breakKeepAliveLoop(socketWrapper)) {
break;
}
}

rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);

if (getErrorState().isError() || endpoint.isPaused()) {
return SocketState.CLOSED;
} else if (isAsync() || comet) {
return SocketState.LONG;
} else if (isUpgrade()) {
return SocketState.UPGRADING;
} else if (getUpgradeInbound() != null) {
return SocketState.UPGRADING_TOMCAT;
} else {
if (sendfileInProgress) {
return SocketState.SENDFILE;
} else {
if (openSocket) {
if (readComplete) {
return SocketState.OPEN;
} else {
return SocketState.LONG;
}
} else {
return SocketState.CLOSED;
}
}
}
}
/**
* Send an action to the connector.
*
* @param actionCode Type of the action
* @param param Action parameter
*/
@Override
@SuppressWarnings("deprecation") // Inbound/Outbound based upgrade mechanism
public final void action(ActionCode actionCode, Object param) {

switch (actionCode) {
case CLOSE: {
// End the processing of the current request
try {
getOutputBuffer().endRequest();
} catch (IOException e) {
setErrorState(ErrorState.CLOSE_NOW, e);
}
break;
}
case COMMIT: {
// Commit current response
if (response.isCommitted()) {
return;
}

// Validate and write response headers
try {
prepareResponse();
getOutputBuffer().commit();
} catch (IOException e) {
setErrorState(ErrorState.CLOSE_NOW, e);
}
break;
}
case ACK: {
// Acknowledge request
// Send a 100 status back if it makes sense (response not committed
// yet, and client specified an expectation for 100-continue)
if ((response.isCommitted()) || !expectation) {
return;
}

getInputBuffer().setSwallowInput(true);
try {
getOutputBuffer().sendAck();
} catch (IOException e) {
setErrorState(ErrorState.CLOSE_NOW, e);
}
break;
}
case CLIENT_FLUSH: {
try {
getOutputBuffer().flush();
} catch (IOException e) {
setErrorState(ErrorState.CLOSE_NOW, e);
response.setErrorException(e);
}
break;
}
case IS_ERROR: {
((AtomicBoolean) param).set(getErrorState().isError());
break;
}
case DISABLE_SWALLOW_INPUT: {
// Do not swallow request input and make sure we are closing the
// connection
setErrorState(ErrorState.CLOSE_CLEAN, null);
getInputBuffer().setSwallowInput(false);
break;
}
case RESET: {
// Note: This must be called before the response is committed
getOutputBuffer().reset();
break;
}
case CUSTOM: {
// Do nothing
// TODO Remove this action
break;
}
case REQ_SET_BODY_REPLAY: {
ByteChunk body = (ByteChunk) param;

InputFilter savedBody = new SavedRequestInputFilter(body);
savedBody.setRequest(request);

@SuppressWarnings("unchecked")
AbstractInputBuffer<S> internalBuffer = (AbstractInputBuffer<S>)
request.getInputBuffer();
internalBuffer.addActiveFilter(savedBody);
break;
}
case ASYNC_START: {
asyncStateMachine.asyncStart((AsyncContextCallback) param);
// Async time out is based on SocketWrapper access time
getSocketWrapper().access();
break;
}
case ASYNC_DISPATCHED: {
asyncStateMachine.asyncDispatched();
break;
}
case ASYNC_TIMEOUT: {
AtomicBoolean result = (AtomicBoolean) param;
result.set(asyncStateMachine.asyncTimeout());
break;
}
case ASYNC_RUN: {
asyncStateMachine.asyncRun((Runnable) param);
break;
}
case ASYNC_ERROR: {
asyncStateMachine.asyncError();
break;
}
case ASYNC_IS_STARTED: {
((AtomicBoolean) param).set(asyncStateMachine.isAsyncStarted());
break;
}
case ASYNC_IS_COMPLETING: {
((AtomicBoolean) param).set(asyncStateMachine.isCompleting());
break;
}
case ASYNC_IS_DISPATCHING: {
((AtomicBoolean) param).set(asyncStateMachine.isAsyncDispatching());
break;
}
case ASYNC_IS_ASYNC: {
((AtomicBoolean) param).set(asyncStateMachine.isAsync());
break;
}
case ASYNC_IS_TIMINGOUT: {
((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
break;
}
case ASYNC_IS_ERROR: {
((AtomicBoolean) param).set(asyncStateMachine.isAsyncError());
break;
}
case UPGRADE_TOMCAT: {
upgradeInbound = (org.apache.coyote.http11.upgrade.UpgradeInbound) param;
// Stop further HTTP output
getOutputBuffer().finished = true;
break;
}
case ASYNC_POST_PROCESS: {
asyncStateMachine.asyncPostProcess();
break;
}
case UPGRADE: {
httpUpgradeHandler = (HttpUpgradeHandler) param;
// Stop further HTTP output
getOutputBuffer().finished = true;
break;
}
case CLOSE_NOW: {
// Block further output
getOutputBuffer().finished = true;
if (param instanceof Throwable) {
setErrorState(ErrorState.CLOSE_NOW, (Throwable) param);
} else {
setErrorState(ErrorState.CLOSE_NOW, null);
}
break;
}
case END_REQUEST: {
endRequest();
break;
}
default: {
actionInternal(actionCode, param);
break;
}
}
}
/**
* When committing the response, we have to validate the set of headers, as
* well as setup the response filters.
*/
private void prepareResponse() throws IOException {

boolean entityBody = true;
contentDelimitation = false;

OutputFilter[] outputFilters = getOutputBuffer().getFilters();

if (http09 == true) {
// HTTP/0.9
getOutputBuffer().addActiveFilter
(outputFilters[Constants.IDENTITY_FILTER]);
return;
}

int statusCode = response.getStatus();
if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
statusCode == 304) {
// No entity body
getOutputBuffer().addActiveFilter
(outputFilters[Constants.VOID_FILTER]);
entityBody = false;
contentDelimitation = true;
if (statusCode == 205) {
// RFC 7231 requires the server to explicitly signal an empty
// response in this case
response.setContentLength(0);
} else {
response.setContentLength(-1);
}
}

MessageBytes methodMB = request.method();
if (methodMB.equals("HEAD")) {
// No entity body
getOutputBuffer().addActiveFilter
(outputFilters[Constants.VOID_FILTER]);
contentDelimitation = true;
}

// Sendfile support
boolean sendingWithSendfile = false;
if (getEndpoint().getUseSendfile()) {
sendingWithSendfile = prepareSendfile(outputFilters);
}

// Check for compression
boolean isCompressible = false;
boolean useCompression = false;
if (entityBody && (compressionLevel > 0) && !sendingWithSendfile) {
isCompressible = isCompressible();
if (isCompressible) {
useCompression = useCompression();
}
// Change content-length to -1 to force chunking
if (useCompression) {
response.setContentLength(-1);
}
}

MimeHeaders headers = response.getMimeHeaders();
// A SC_NO_CONTENT response may include entity headers
if (entityBody || statusCode == 204) {
String contentType = response.getContentType();
if (contentType != null) {
headers.setValue("Content-Type").setString(contentType);
}
String contentLanguage = response.getContentLanguage();
if (contentLanguage != null) {
headers.setValue("Content-Language")
.setString(contentLanguage);
}
}

long contentLength = response.getContentLengthLong();
boolean connectionClosePresent = isConnectionToken(headers, Constants.CLOSE);
if (contentLength != -1) {
headers.setValue("Content-Length").setLong(contentLength);
getOutputBuffer().addActiveFilter
(outputFilters[Constants.IDENTITY_FILTER]);
contentDelimitation = true;
} else {
// If the response code supports an entity body and we're on
// HTTP 1.1 then we chunk unless we have a Connection: close header
if (http11 && entityBody && !connectionClosePresent) {
getOutputBuffer().addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
contentDelimitation = true;
headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);
} else {
getOutputBuffer().addActiveFilter
(outputFilters[Constants.IDENTITY_FILTER]);
}
}

if (useCompression) {
getOutputBuffer().addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
headers.setValue("Content-Encoding").setString("gzip");
}
// If it might be compressed, set the Vary header
if (isCompressible) {
ResponseUtil.addVaryFieldName(headers, "accept-encoding");
}

// Add date header unless application has already set one (e.g. in a
// Caching Filter)
if (headers.getValue("Date") == null) {
headers.setValue("Date").setString(
FastHttpDateFormat.getCurrentDate());
}

// FIXME: Add transfer encoding header

if ((entityBody) && (!contentDelimitation)) {
// Mark as close the connection after the request, and add the
// connection: close header
keepAlive = false;
}

// This may disabled keep-alive to check before working out the
// Connection header.
checkExpectationAndResponseStatus();

// If we know that the request is bad this early, add the
// Connection: close header.
keepAlive = keepAlive && !statusDropsConnection(statusCode);
if (!keepAlive) {
// Avoid adding the close header twice
if (!connectionClosePresent) {
headers.addValue(Constants.CONNECTION).setString(
Constants.CLOSE);
}
} else if (!http11 && !getErrorState().isError()) {
headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);
}

// Build the response header
getOutputBuffer().sendStatus();

// Add server header
if (server != null) {
// Always overrides anything the app might set
headers.setValue("Server").setString(server);
} else if (headers.getValue("Server") == null) {
// If app didn't set the header, use the default
getOutputBuffer().write(Constants.SERVER_BYTES);
}

int size = headers.size();
for (int i = 0; i < size; i++) {
getOutputBuffer().sendHeader(headers.getName(i), headers.getValue(i));
}
getOutputBuffer().endHeaders();

}
}

final class ApplicationDispatcher implements AsyncDispatcher, RequestDispatcher {
private void doForward(ServletRequest request, ServletResponse response)
throws ServletException, IOException
{

// Reset any output that has been buffered, but keep headers/cookies
if (response.isCommitted()) {
throw new IllegalStateException
(sm.getString("applicationDispatcher.forward.ise"));
}
try {
response.resetBuffer();
} catch (IllegalStateException e) {
throw e;
}

// Set up to handle the specified request and response
State state = new State(request, response, false);

if (WRAP_SAME_OBJECT) {
// Check SRV.8.2 / SRV.14.2.5.1 compliance
checkSameObjects(request, response);
}

wrapResponse(state);
// Handle an HTTP named dispatcher forward
if ((servletPath == null) && (pathInfo == null)) {

ApplicationHttpRequest wrequest =
(ApplicationHttpRequest) wrapRequest(state);
HttpServletRequest hrequest = state.hrequest;
wrequest.setRequestURI(hrequest.getRequestURI());
wrequest.setContextPath(hrequest.getContextPath());
wrequest.setServletPath(hrequest.getServletPath());
wrequest.setPathInfo(hrequest.getPathInfo());
wrequest.setQueryString(hrequest.getQueryString());

processRequest(request,response,state);
}

// Handle an HTTP path-based forward
else {

ApplicationHttpRequest wrequest =
(ApplicationHttpRequest) wrapRequest(state);
HttpServletRequest hrequest = state.hrequest;
if (hrequest.getAttribute(
RequestDispatcher.FORWARD_REQUEST_URI) == null) {
wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI,
hrequest.getRequestURI());
wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH,
hrequest.getContextPath());
wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH,
hrequest.getServletPath());
wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO,
hrequest.getPathInfo());
wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING,
hrequest.getQueryString());
}

wrequest.setContextPath(context.getEncodedPath());
wrequest.setRequestURI(requestURI);
wrequest.setServletPath(servletPath);
wrequest.setPathInfo(pathInfo);
if (queryString != null) {
wrequest.setQueryString(queryString);
wrequest.setQueryParams(queryString);
}

processRequest(request,response,state);
}

if (request.isAsyncStarted()) {
// An async request was started during the forward, don't close the
// response as it may be written to during the async handling
return;
}

// This is not a real close in order to support error processing
if (wrapper.getLogger().isDebugEnabled() )
wrapper.getLogger().debug(" Disabling the response for further output");

if (response instanceof ResponseFacade) {
((ResponseFacade) response).finish();
} else {
// Servlet SRV.6.2.2. The Request/Response may have been wrapped
// and may no longer be instance of RequestFacade
if (wrapper.getLogger().isDebugEnabled()){
wrapper.getLogger().debug( " The Response is vehiculed using a wrapper: "
+ response.getClass().getName() );
}

// Close anyway
try {
PrintWriter writer = response.getWriter();
writer.close();
} catch (IllegalStateException e) {
try {
ServletOutputStream stream = response.getOutputStream();
stream.close();
} catch (IllegalStateException f) {
// Ignore
} catch (IOException f) {
// Ignore
}
} catch (IOException e) {
// Ignore
}
}

}

}

public class Response implements HttpServletResponse {
/**
* Send an error response with the specified status and message.
*
* @param status HTTP status code to send
* @param message Corresponding message to send
*
* @exception IllegalStateException if this response has
* already been committed
* @exception IOException if an input/output error occurs
*/
@Override
public void sendError(int status, String message) throws IOException {

if (isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteResponse.sendError.ise"));
}

// Ignore any call from an included servlet
if (included) {
return;
}

setError();

getCoyoteResponse().setStatus(status);
getCoyoteResponse().setMessage(message);

// Clear any data content that has been buffered
resetBuffer();

// Cause the response to be finished (from the application perspective)
setSuspended(true);
}
}
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
/**
* Add an error page for the specified error or Java exception.
*
* @param errorPage The error page definition to be added
*/
@Override
public void addErrorPage(ErrorPage errorPage) {
// Validate the input parameters
if (errorPage == null)
throw new IllegalArgumentException
(sm.getString("standardContext.errorPage.required"));
String location = errorPage.getLocation();
if ((location != null) && !location.startsWith("/")) {
if (isServlet22()) {
if(log.isDebugEnabled())
log.debug(sm.getString("standardContext.errorPage.warning",
location));
errorPage.setLocation("/" + location);
} else {
throw new IllegalArgumentException
(sm.getString("standardContext.errorPage.error",
location));
}
}

// Add the specified error page to our internal collections
String exceptionType = errorPage.getExceptionType();
if (exceptionType != null) {
synchronized (exceptionPages) {
exceptionPages.put(exceptionType, errorPage);
}
} else {
synchronized (statusPages) {
if (errorPage.getErrorCode() == 200) {
this.okErrorPage = errorPage;
}
statusPages.put(Integer.valueOf(errorPage.getErrorCode()),
errorPage);
}
}
fireContainerEvent("addErrorPage", errorPage);

}
}
/**
* Valve that implements the default basic behavior for the
* <code>StandardHost</code> container implementation.
* <p>
* <b>USAGE CONSTRAINT</b>: This implementation is likely to be useful only
* when processing HTTP requests.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
final class StandardHostValve extends ValveBase {
/**
* Handle an HTTP status code or Java exception by forwarding control
* to the location included in the specified errorPage object. It is
* assumed that the caller has already recorded any request attributes
* that are to be forwarded to this page. Return <code>true</code> if
* we successfully utilized the specified error page location, or
* <code>false</code> if the default error report should be rendered.
*
* @param request The request being processed
* @param response The response being generated
* @param errorPage The errorPage directive we are obeying
*/
private boolean custom(Request request, Response response,
ErrorPage errorPage) {

if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("Processing " + errorPage);
}

try {
// Forward control to the specified location
ServletContext servletContext =
request.getContext().getServletContext();
RequestDispatcher rd =
servletContext.getRequestDispatcher(errorPage.getLocation());

if (rd == null) {
container.getLogger().error(
sm.getString("standardHostValue.customStatusFailed", errorPage.getLocation()));
return false;
}

if (response.isCommitted()) {
// Response is committed - including the error page is the
// best we can do
rd.include(request.getRequest(), response.getResponse());
} else {
// Reset the response (keeping the real error code and message)
response.resetBuffer(true);
response.setContentLength(-1);

rd.forward(request.getRequest(), response.getResponse());

// If we forward, the response is suspended again
response.setSuspended(false);
}

// Indicate that we have successfully processed this custom page
return true;

} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// Report our failure to process this custom page
container.getLogger().error("Exception Processing " + errorPage, t);
return false;
}
}
}

public class ErrorReportValve extends ValveBase {
/**
* Invoke the next Valve in the sequence. When the invoke returns, check
* the response state. If the status code is greater than or equal to 400
* or an uncaught exception was thrown then the error handling will be
* triggered.
*
* @param request The servlet request to be processed
* @param response The servlet response to be created
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
@Override
public void invoke(Request request, Response response) throws IOException, ServletException {

// Perform the request
getNext().invoke(request, response);

if (response.isCommitted()) {
if (response.setErrorReported()) {
// Error wasn't previously reported but we can't write an error
// page because the response has already been committed. Attempt
// to flush any data that is still to be written to the client.
try {
response.flushBuffer();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
// Close immediately to signal to the client that something went
// wrong
response.getCoyoteResponse().action(ActionCode.CLOSE_NOW,
request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
}
return;
}

Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

// If an async request is in progress and is not going to end once this
// container thread finishes, do not process any error page here.
if (request.isAsync() && !request.isAsyncCompleting()) {
return;
}

if (throwable != null && !response.isError()) {
// Make sure that the necessary methods have been called on the
// response. (It is possible a component may just have set the
// Throwable. Tomcat won't do that but other components might.)
// These are safe to call at this point as we know that the response
// has not been committed.
response.reset();
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}

// One way or another, response.sendError() will have been called before
// execution reaches this point and suspended the response. Need to
// reverse that so this valve can write to the response.
response.setSuspended(false);

try {
report(request, response, throwable);
} catch (Throwable tt) {
ExceptionUtils.handleThrowable(tt);
}
}
/**
* Prints out an error report. 渲染默认错误页面
*
* @param request The request being processed
* @param response The response being generated
* @param throwable The exception that occurred (which possibly wraps
* a root cause exception
*/
protected void report(Request request, Response response, Throwable throwable) {

int statusCode = response.getStatus();

// Do nothing on a 1xx, 2xx and 3xx status
// Do nothing if anything has been written already
// Do nothing if the response hasn't been explicitly marked as in error
// and that error has not been reported.
if (statusCode < 400 || response.getContentWritten() > 0 || !response.setErrorReported()) {
return;
}
String message = RequestUtil.filter(response.getMessage());
if (message == null) {
if (throwable != null) {
String exceptionMessage = throwable.getMessage();
if (exceptionMessage != null && exceptionMessage.length() > 0) {
message = RequestUtil.filter((new Scanner(exceptionMessage)).nextLine());
}
}
if (message == null) {
message = "";
}
}

// Do nothing if there is no reason phrase for the specified status code and
// no error message provided
String reason = null;
String description = null;
StringManager smClient = StringManager.getManager(
Constants.Package, request.getLocales());
response.setLocale(smClient.getLocale());
try {
reason = smClient.getString("http." + statusCode + ".reason");
description = smClient.getString("http." + statusCode + ".desc");
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
}
if (reason == null || description == null) {
if (message.isEmpty()) {
return;
} else {
reason = smClient.getString("errorReportValve.unknownReason");
description = smClient.getString("errorReportValve.noDescription");
}
}

StringBuilder sb = new StringBuilder();

sb.append("<!doctype html><html lang=\"");
sb.append(smClient.getLocale().getLanguage()).append("\">");
sb.append("<head>");
sb.append("<title>");
sb.append(smClient.getString("errorReportValve.statusHeader",
String.valueOf(statusCode), reason));
sb.append("</title>");
sb.append("<style type=\"text/css\">");
sb.append(TomcatCSS.TOMCAT_CSS);
sb.append("</style>");
sb.append("</head><body>");
sb.append("<h1>");
sb.append(smClient.getString("errorReportValve.statusHeader",
String.valueOf(statusCode), reason)).append("</h1>");
if (isShowReport()) {
sb.append("<hr class=\"line\" />");
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.type"));
sb.append("</b> ");
if (throwable != null) {
sb.append(smClient.getString("errorReportValve.exceptionReport"));
} else {
sb.append(smClient.getString("errorReportValve.statusReport"));
}
sb.append("</p>");
if (!message.isEmpty()) {
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.message"));
sb.append("</b> ");
sb.append(message).append("</p>");
}
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.description"));
sb.append("</b> ");
sb.append(description);
sb.append("</p>");
if (throwable != null) {
String stackTrace = getPartialServletStackTrace(throwable);
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.exception"));
sb.append("</b> <pre>");
sb.append(RequestUtil.filter(stackTrace));
sb.append("</pre></p>");

int loops = 0;
Throwable rootCause = throwable.getCause();
while (rootCause != null && (loops < 10)) {
stackTrace = getPartialServletStackTrace(rootCause);
sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.rootCause"));
sb.append("</b> <pre>");
sb.append(RequestUtil.filter(stackTrace));
sb.append("</pre></p>");
// In case root cause is somehow heavily nested
rootCause = rootCause.getCause();
loops++;
}

sb.append("<p><b>");
sb.append(smClient.getString("errorReportValve.note"));
sb.append("</b> ");
sb.append(smClient.getString("errorReportValve.rootCauseInLogs"));
sb.append("</p>");

}
sb.append("<hr class=\"line\" />");
}
if (isShowServerInfo()) {
sb.append("<h3>").append(ServerInfo.getServerInfo()).append("</h3>");
}
sb.append("</body></html>");

try {
try {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
if (container.getLogger().isDebugEnabled()) {
container.getLogger().debug("status.setContentType", t);
}
}
Writer writer = response.getReporter();
if (writer != null) {
// If writer is null, it's an indication that the response has
// been hard committed already, which should never happen
writer.write(sb.toString());
response.finishResponse();
}
} catch (IOException e) {
// Ignore
} catch (IllegalStateException e) {
// Ignore
}

}
}

public class StandardHost extends ContainerBase implements Host {
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {

// Set error report valve
String errorValve = getErrorReportValveClass(); // 没做修改的话就是获取默认ErrorReportValue
if ((errorValve != null) && (!errorValve.equals(""))) {
try {
boolean found = false;
Valve[] valves = getPipeline().getValves();
for (Valve valve : valves) {
if (errorValve.equals(valve.getClass().getName())) {
found = true;
break;
}
}
if(!found) {
Valve valve =
(Valve) Class.forName(errorValve).getConstructor().newInstance();
getPipeline().addValve(valve); // 添加到Pileline末尾
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"standardHost.invalidErrorReportValveClass",
errorValve), t);
}
}
super.startInternal();
}

部署

多个应用部署在同一个tomcat有好有坏吧,好处维护少一点,得一起开一起关闭。

问题排查

出问题最先该做的就是打开日志,查看日志。

如何修改上传文件的默认权限

Tomcat Linux修改上传文件默认权限
编辑tomcat根目录/bin/catalina.sh文件,找到

1
2
3
4
5
# Set UMASK unless it has been overridden
if [ -z "$UMASK" ]; then
UMASK="0027"
fi
umask $UMASK

将0027根据需要改成0022或者其他,重启

修改https配置

如果把https修改成http,浏览器需要关闭重新开,不然无法登录,可能浏览器会认为服务器支持https,走到https流程。

支持IPv6

tomcat8支持ipv6与ipv4。虽然netstat -tnl 只显示ipv6,其实是都支持的,可以到本机使用telnet ipv6与端口进行尝试。如果其他服务器无法通过ipV6连接到对方服务大概率就算使用的交换机或者路由器不支持IPv6了。
可以到server.xml中添加address参数详细配置监听地址。

提供文件服务

增加一个配置项映射,就可以跟Nginx一样对外提供文件访问服务。

1
<Context path="/abc-data" docBase="/data/ftp/images"/>

升级包可能出现问题

升级包tar.gz不要把原先目录改成文件,这样同名文件无法覆盖成功会出问题。

版本运维

通过jenkins自动从svn拉取项目自动部署还是挺好用的。

问题

webapps里面相同项目拷贝了两个,一个叫做xxx_bak结果代码功能都是一样的导致冲突问题。看日志可以看出来,程序重复初始化2次等等。

基于Tomcat的WEB应用使用到了Kafka服务

有遇到过tomcat内部文件权限被改错,导致整个程序起来的时候IO吃到满的情况,用root用户启动就正常,把tomcat内部文件权限都改对了就可以正常启动了。
进行了如下尝试最后正常了:
可能不完全是权限问题,有把kafka注释就能启动
也可能groupid冲突了,修改groupid,两个用到的程序都得重启
尝试使用root权限启动tomcat
普通用户情况可能war包也不能是root权限,修改tomcat下所有文件权限为普通用用户,普通用户重启,正常启动

最可能的是:tomcat下已有且只有非root用户解压出来的文件夹,此时上传root权限的war包会导致大量IOwait

JDK TOMCAT版本对应关系 Servlet

项目启动的时候会有提示信息,可以根据提升选择版本。
Maven Springboot项目可以搜索spring-boot-starter-tomcat 2.7.x默认集成的是tomcat9,使用servlet4.0。

启动慢问题

openjdk环境中java项目启动慢的问题
可能是openjdk在启动时会去获取hostname,如果未设置就会卡很久。

在服务器上/etc/hosts中加入ip与hostname即可解决,hostname命令查看主机名

echo "21.22.22.xx localhost localhost.localdomain" >> /etc/hosts

现网成功解决启动慢问题

参考