总述
JDK都要出12了,而我们项目使用的jdk却仍然还停留在JDK1.6。为了追寻技术的发展的脚步,我这边准备将项目升级到JDK1.8。而作为一个web项目,我们的容器使用的是Tomcat。看了下Tomcat版本与JDK版本之间的兼容关系http://tomcat.apache.org/whichversion.html以及网上所传的各种JDK1.8和Tomcat7不兼容的问题, 我决定将Tomcat升级到8。我这里本地验证采用的tomcat版本是8.5.38https://tomcat.apache.org/download-80.cgi。
问题一:请求js文件报404错误
其实这个问题严格来讲不是升级到Tomcat8出现的问题,而是升级到Tomcat9出现的问题。正好我开始尝试的是Tomcat9,无法解决这个问题才降到Tomcat8。所以这里一并记录下来。
这个问题在从Tomcat6升级到Tomcat7之后也会存在,原因如下,在项目代码中对js的请求路径中包含了{、}
等特殊符号:
<script type="text/javascript" src="${ctx}/js/common/include_css.js?{'ctx':'${ctx}','easyui':'easyui'}"></script>
前台会发现加载js的时候报了404的错误,后台报错信息如下:
Invalid character found in the request target.The valid characters are defined in RFC 7230 and RFC3986
出现这个问题的原因是因为Tomcat升级之后对安全进行了升级,其中就有对请求中的特殊字符进行校验,具体校验规则参照下面的代码:
(InternalInputBuffer、InternalAprInputBuffer、InternalNioInputBuffer)
/**
* Read the request line. This function is meant to be used during the
* HTTP request header parsing. Do NOT attempt to read the request body
* using it.
*
* @throws IOException If an exception occurs during the underlying socket
* read operations, or if the given buffer is not big enough to accommodate
* the whole line.
*/
@Override
public boolean parseRequestLine(boolean useAvailableDataOnly)
throws IOException {
int start = 0;
//
// Skipping blank lines
//
byte chr = 0;
do {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
// Set the start time once we start reading data (even if it is
// just skipping blank lines)
if (request.getStartTime() < 0) {
request.setStartTime(System.currentTimeMillis());
}
chr = buf[pos++];
} while ((chr == Constants.CR) || (chr == Constants.LF));
pos--;
// Mark the current buffer position
start = pos;
//
// Reading the method name
// Method name is a token
//
boolean space = false;
while (!space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
// Spec says method name is a token followed by a single SP but
// also be tolerant of multiple SP and/or HT.
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
space = true;
request.method().setBytes(buf, start, pos - start);
} else if (!HttpParser.isToken(buf[pos])) {
throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
}
pos++;
}
// Spec says single SP but also be tolerant of multiple SP and/or HT
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
pos++;
} else {
space = false;
}
}
// Mark the current buffer position
start = pos;
int end = 0;
int questionPos = -1;
//
// Reading the URI
//
boolean eol = false;
while (!space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
// Spec says single SP but it also says be tolerant of HT
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
space = true;
end = pos;
} else if ((buf[pos] == Constants.CR)
|| (buf[pos] == Constants.LF)) {
// HTTP/0.9 style request
eol = true;
space = true;
end = pos;
} else if ((buf[pos] == Constants.QUESTION) && (questionPos == -1)) {
questionPos = pos;
} else if (HttpParser.isNotRequestTarget(buf[pos])) {
throw new IllegalArgumentException(sm.getString("iib.invalidRequestTarget"));
}
pos++;
}
request.unparsedURI().setBytes(buf, start, end - start);
if (questionPos >= 0) {
request.queryString().setBytes(buf, questionPos + 1,
end - questionPos - 1);
request.requestURI().setBytes(buf, start, questionPos - start);
} else {
request.requestURI().setBytes(buf, start, end - start);
}
// Spec says single SP but also says be tolerant of multiple SP and/or HT
while (space) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
pos++;
} else {
space = false;
}
}
// Mark the current buffer position
start = pos;
end = 0;
//
// Reading the protocol
// Protocol is always "HTTP/" DIGIT "." DIGIT
//
while (!eol) {
// Read new bytes if needed
if (pos >= lastValid) {
if (!fill())
throw new EOFException(sm.getString("iib.eof.error"));
}
if (buf[pos] == Constants.CR) {
end = pos;
} else if (buf[pos] == Constants.LF) {
if (end == 0)
end = pos;
eol = true;
} else if (!HttpParser.isHttpProtocol(buf[pos])) {
// 关键点在这一句,如果校验不通过,则会报参数异常
throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));
}
pos++;
}
if ((end - start) > 0) {
request.protocol().setBytes(buf, start, end - start);
} else {
request.protocol().setString("");
}
return true;
}
我们进一步跟进HttpParser
中的方法:
public static boolean isNotRequestTarget(int c) {
// Fast for valid request target characters, slower for some incorrect
// ones
try {
// 关键在于这个数组
return IS_NOT_REQUEST_TARGET[c];
} catch (ArrayIndexOutOfBoundsException ex) {
return true;
}
}
// Combination of multiple rules from RFC7230 and RFC 3986. Must be
// ASCII, no controls plus a few additional characters excluded
if (IS_CONTROL[i] || i > 127 ||
i == ' ' || i == '\"' || i == '#' || i == '<' || i == '>' || i == '\\' ||
i == '^' || i == '`' || i == '{' || i == '|' || i == '}') {
// 可以看到只有在REQUEST_TARGET_ALLOW数组中的值才不会设置成true,所以我们需要追踪REQUEST_TARGET_ALLOW数组的赋值
if (!REQUEST_TARGET_ALLOW[i]) {
IS_NOT_REQUEST_TARGET[i] = true;
}
}
String prop = System.getProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow");
if (prop != null) {
for (int i = 0; i < prop.length(); i++) {
char c = prop.charAt(i);
// 可以看到在配置文件中配置了tomcat.util.http.parser.HttpParser.requestTargetAllow并且包含{、}、|的时候,REQUEST_TARGET_ALLOW数组中的值才会为true
if (c == '{' || c == '}' || c == '|') {
REQUEST_TARGET_ALLOW[c] = true;
} else {
log.warn(sm.getString("httpparser.invalidRequestTargetCharacter",
Character.valueOf(c)));
}
}
}
解决办法: 其实通过源码分析不难得到解决办法
在Tomcat的catalina.properties文件中添加以下语句:
tomcat.util.http.parser.HttpParser.requestTargetAllow={}|
当然需要注意的是,这个后门
在Tomcat8.5以后就无法使用的,Tomcat9之后的解决办法暂时未找到,可能只有对URL进行编码了。
问题二:Cookie设置报错
这个问题就是在升级到Tomcat8.5以上的时候会出现的,具体原因是Tomcat8.5采用的Cookie处理类是:
Rfc6265CookieProcessor
,而在之前使用的处理类是LegacyCookieProcessor
。该处理类对domai进行了校验:
private void validateDomain(String domain) {
int i = 0;
int prev = -1;
int cur = -1;
char[] chars = domain.toCharArray();
while (i < chars.length) {
prev = cur;
cur = chars[i];
if (!domainValid.get(cur)) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
// labels must start with a letter or number
if ((prev == '.' || prev == -1) && (cur == '.' || cur == '-')) {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
// labels must end with a letter or number
if (prev == '-' && cur == '.') {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
i++;
}
// domain must end with a label
if (cur == '.' || cur == '-') {
throw new IllegalArgumentException(sm.getString(
"rfc6265CookieProcessor.invalidDomain", domain));
}
}
新的Cookie规范对domain有以下要求
1、必须是1-9、a-z、A-Z、. 、- (注意是-不是_)这几个字符组成
2、必须是数字或字母开头 (所以以前的cookie的设置为.XX.com 的机制要改为 XX.com 即可)
3、必须是数字或字母结尾
原来的代码设置domain时如下:
cookie.setDomain(".aaa.com");
这就导致设置domain的时候不符合新的规范,直接报错如下:
java.lang.IllegalArgumentException: An invalid domain [.aaa.com] was specified for this cookie
at org.apache.tomcat.util.http.Rfc6265CookieProcessor.validateDomain(Rfc6265CookieProcessor.java:181)
at org.apache.tomcat.util.http.Rfc6265CookieProcessor.generateHeader(Rfc6265CookieProcessor.java:123)
at org.apache.catalina.connector.Response.generateCookieString(Response.java:989)
at org.apache.catalina.connector.Response.addCookie(Response.java:937)
at org.apache.catalina.connector.ResponseFacade.addCookie(ResponseFacade.java:386)
解决办法(以下3中任意一种皆可)
-
修改原来代码为:
cookie.setDomain("aaa.com");
-
如果是Spring-boot环境,直接替换默认的Cookie处理类:
@Configuration
@ConditionalOnExpression("${tomcat.useLegacyCookieProcessor:false}")
public class LegacyCookieProcessorConfiguration {
@Bean
EmbeddedServletContainerCustomizer embeddedServletContainerCustomizerLegacyCookieProcessor() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer factory) {
if (factory instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory tomcatFactory =
(TomcatEmbeddedServletContainerFactory) factory;
tomcatFactory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
context.setCookieProcessor(new LegacyCookieProcessor());
}
});
}
}
};
}
} -
在Tomcat的context.xml中增加如下配置,指定Cookie的处理类:
<CookieProcessor className="org.apache.tomcat.util.http.LegacyCookieProcessor" />
参考链接
https://blog.csdn.net/fy_sun123/article/details/73115381
http://ju.outofmemory.cn/entry/367186
https://www.cnblogs.com/lr393993507/p/7755867.html
http://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html
记一次升级Tomcat的更多相关文章
-
Tomcat8.5 升级tomcat版本导致出现异常,Base64不存在
Tomcat8.5 升级tomcat版本导致出现异常,Base64不存在 原因分析: 由于tomcat由7升级到8.5导致Base64的引用路径错误,默认引用为8.5中的jar, 解决方案: 修改引用 ...
-
害你加班的bug就是我写的,记一次升级Jenkins插件引发的加班
主旨 本文主要记录了下Jenkins升级插件过程中出现的场景,一次加班经历,事发时没有截图,有兴趣可以看看. 起因 需求 最近有个需求:在Jenkins流水线中完成下载Git上的文件简单修改并提交的功 ...
-
记一次在Tomcat部署项目后无法启动该项目的例子
在一次正常部署后,发现在Tomcat的管理中无法将该项目启动起来,并在Tomcat的日志文件localhost.log中发现以下错误信息: java.lang.NoSuchMethodError: o ...
-
记一次服务器Tomcat优化经历
公司需要一台测试服务器来做测试用,所以花了几天时间把服务全部部署好,在部署好war包之后,发现Tomcat访问超级慢. 1.进入Tomcat的bin目录下,运行 ./catalina.sh run命令 ...
-
升级tomcat需要更改哪些配置?
1.上传Tomcatapache-tomcat-7.0.84.zip将38服务器上的Tomcat传到107服务器指定目录:scp /data/apache-tomcat-7.0.84.zip jsdx ...
-
记一次升级Ubuntu内核
uname -a 查看当前使用内核版本 升级指定内核 apt-get install linux-image-4.4.0-131-generic dpkg --get-selections | ...
-
记一次排查tomcat耗费CPU过高的经历
有一个新项目,在测试环境部署后,发现tomcat进程耗费的CPU非常高,排查过程如下: 日志搜集 先通过top,查找耗费CPU最高的线程 top -Hp pid 将线程ID转为16进制 printf ...
-
记一次解决tomcat自动关闭的bug
最近一个运行了4年的javaee web项目,经常接到客户反馈系统无法打开.登录服务器查看服务,发现是tomcat自动关闭了.基本是3到4天发生一次. 运维人员开始以为是其他服务杀死了tomcat服务 ...
-
记一次启动Tomcat 控制台以及log4j 乱码问题
Tomcat启动乱码 问题描述:当你发现你的Tomcat启动时乱码了,而你只是换了个Tomcat版本而已. 在找到真正的问题之前,我在网上百度了N多的资料,都试过了,但是都不行.1.修改了 windo ...
随机推荐
-
VMware 中如何打开U盘弹出U盘或者移动硬盘的(两种方法)
1.U盘如下,插入后都是直接在win里面显示的 2.选择连接u盘 3.u盘就可以在虚拟机里面显示了 4.弹出则选择断开连接 扩展:如果无效:请参考这种方法 (给虚拟机分配一个临时硬盘,然后设置这个临时 ...
-
iptables常用操作
1.iptables服务重启 service iptables restart 2.保存iptables规则 iptables-save > ~/iptables.save 3.恢复iptabl ...
-
【Qt】Qt之重启应用程序【转】
简介 今天分享的内容有些意思-如何重启一个应用程序.其实,有时候这是一个很重要的功能点,而且很人性化.易用性很好. 例如:切换用户.当某个用户登录成功之后,需要切换到其它账号,那么这时,你就知道它的重 ...
-
unix 常用命令
(一)基本命令 命令格式: 命令 参数 1.ls 显示文件名,等同于dos下dir命令 命令格式:ls [option] file option: -l 显示详细列表 域1 :文件类型和文件权限 域2 ...
-
RxJava开发精要5 – Observables变换
原文出自<RxJava Essentials> 原文作者 : Ivan Morgillo 译文出自 : 开发技术前线 www.devtf.cn 转载声明: 本译文已授权开发者头条享有独家转 ...
-
做了一个类似天猫鼠标经过icon的动画,记录一下
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
-
HDU 5889 Barricade 【BFS+最小割 网络流】(2016 ACM/ICPC Asia Regional Qingdao Online)
Barricade Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total S ...
-
nginx基础入门
nginx常常被用来处理静态资源如css.js.图片.html等,也被用作反向代理server.邮件server,也时常拿来做负载均衡.它的优势主要体如今对静态资源的处理上,这次抽出了点时间整理了一些 ...
-
ELK 架构之 Logstash 和 Filebeat 安装配置
上一篇:ELK 架构之 Elasticsearch 和 Kibana 安装配置 阅读目录: 1. 环境准备 2. 安装 Logstash 3. 配置 Logstash 4. Logstash 采集的日 ...
-
Android 解析标准的点击第三方文件管理器中的视频的intent
解析标准的第三方视频intent private List<String> mCurPlayList = new ArrayList<String>(); private in ...