【技术】Tomcat500异常的两个Case
Photo by Alex Knight on Unsplash
2020年的第 17 篇文章,来自团队小伙伴王伟
问题
背景
线上的Tomcat版本为Tomcat7. JDK版本为1.8
问题现象
某次的上线后,线上出现了大批量的500异常,但业务无错误日志。在tomcat日志里面发现了很多Control character in cookie value or attribute
异常,但日志内容有限,并不能看出来具体是哪些Cookie值存在问题。
异常截图如下:
分析
最直接的办法是,我们下载了Tomcat源码,搜索了异常信息,找到了如下代码。
private boolean isHttpSeparator(char c) {
if ((c < \' \' || c >= 127) && c != \'t\') {
throw new IllegalArgumentException("Control character in cookie value or attribute.");
} else {
return this.httpSeparatorFlags.get(c);
}
}
源码中看到对于cookie中的字符有特殊限制,参考ascii表,如 https://baike.baidu.com/item/ASCII/309296?fr=aladdin 得知不允许特殊字符,所以解决的方案也会比较明确:
方案一:找到目前Cookie中的异常字符,确认问题根源,从源头上去掉异常字符写入。
方案二:去掉Tomcat的异常字符限制。
方案一理论上讲是最彻底的办法,但实际中发现,我们可以找到异常字符,也确认了异常字符是中文。但因为公司上的站点过多,无法找到具体是哪个端写入的中文Cookie。所以实际执行起来,基本不可行。
退而求其次,我们选择了方案二,替换源码使我们能够支持这种字符
查看官方文档我们看到tomcat7在2021年3月31日就不再支持更新维护了,所以我们决定不再在Tomcat7上做操作,而是尝试升级tomcat8。
继续翻看tomcat8官方文档 https://tomcat.apache.org/tomcat-8.0-doc/config/cookie-processor.html,发现如下文字
Note: CookieProcessor is a new configuration element, introduced in Tomcat 8.0.15.The CookieProcessor element allows different cookie parsing configuration in each web application, or globally in the default conf/context.xml file. The legacy cookie parsing algorithm supported only limited global configuration via several system properties. Those system properties are still supported, but are going to be deprecated in favor of this new configuration element.The new RFC6265-compliant implementation is a drop-in replacement for the original legacy one. The legacy implementation remains the default. You can select the implementation by setting className attribute on CookieProcessor element.
通俗的讲就是tomcat8.0.15版本之后可以在conf/context.xml文件中指定cookie处理器。之前的版本不支持。
解决方案
很明显的方案是:我们将tomcat版本升级到8之后,再自定义这个cookie处理器,然后去掉字符限制就可以了。
private boolean isHttpSeparator(final char c) {
// 中文字符过滤,暂时下掉
/**
if (c < 0x20 || c >= 0x7f) {
if (c != 0x09) {
throw new IllegalArgumentException(
"Control character in cookie value or attribute.");
}
}
*/
return httpSeparatorFlags.get(c);
}
将源码中LegacyCookieProcessor类拷贝出来,注释掉代码中的如图部分,然后在context.xml配置文件中配置
配置上线,运行一段时间。cookie字符异常清零,但又出现了新的异常。
另外一个异常
异常
cookie长度超过200,但我们并没有做过类似的限制。
分析
在tomcat7的变更历史中,https://tomcat.apache.org/tomcat-7.0-doc/changelog.html#Tomcat_7.0.106_(violetagg),在7.0.71版本中添加了cookie长度200的限制,原因是tomcat团队认为如果不限制的话超长的cookie可引发堆栈异常,是个bug给修复了,而我们之前的版本是7.0.42.0,因此低版本没有限制。
问题转换成如何修改cookie长度的问题,我们知道tomcat启动需要配置 Connector,因此在Connector可配置的属性中查找
The HTTP Connector element represents a Connector component that supports the HTTP/1.1 protocol. It enables Catalina to function as a stand-alone web server, in addition to its ability to execute servlets and JSP pages. A particular instance of this component listens for connections on a specific TCP port number on the server. One or more such Connectors can be configured as part of a single Service, each forwarding to the associated Engine to perform request processing and create the response.
进一步翻看tomcat文档 https://tomcat.apache.org/tomcat-8.0-doc/config/http.html我们找到了这两个属性,设置下看看
修改之后,启动,发现问题解决。
打印过长的cookie
但问题并没有彻底解决,理论上一个站点的cookie长度一般都够用,这个200应该能满足大多数的需要,所以,我们需要找到具体是哪些场景下的cookie会多于200个。我们修改了下tomcat源码,增加了cookie打印。在源码处理cookie之前,判断cookie的长度是否大于某个值(我这里设置的200),超过之后打印
上线之后观察,发现超长Cookie为非法请求,于是问题就变简单了,我们限制了这些异常请求,之后500异常量便降了下来。
关于我及张二蛋又要扯蛋了
一个不务正业的程序猿及这个程序猿写字的地方,这里可能有技术,有理财,有历史,有总结,有生活,偶尔也扯扯蛋,妥妥的杂货铺,喜欢可关注。
酒已备好,等你来开