一个有意思的Tomcat 异常

在公众号后台,经常能看到读者的消息,其中一部分消息是关于Tomcat使用过程中遇到的问题。但是,由于微信的「克制」,如果消息回复的比较晚,就会遇到「过期」的尴尬,我并不能主动联系到提问的人。

后面有需要讨论问题的朋友,如果公众号发消息未收到回复,可以加我微信。

说回正题,之前有位读者留言,说了一个 Tomcat 异常的问题。

即 Tomcat 各功能正常,不影响使用,但是偶尔的在日志中会看到类似于这样的异常信息:

INFO [https-apr-8443-exec-5] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header

 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level.

 java.lang.IllegalArgumentException: Invalid character (CR or LF) found in method name

at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:443)

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:982)

为啥报这个呢?明明自己没做什么操作。

顺着异常信息我们往上看,首先这个提示是解析请求头出现的错误。更细节一些是解析请求头中第一行,所谓的「Request Line」的时候出了问题。

什么是「Request Line」呢? 就是HTTP 规范中指定的,以请求方法开头 再加上请求URI 等。具体看这个规范说明

这里我们的异常信息提示我们是在解析 Method name的时候出了问题。看规范里说了「The Request-Line begins with a method token」也就是有固定的东西的,不是啥都能叫一个method name。我们熟悉的GET/POST/PUT/DELETE都是这里允许的。

我们再来看 Tomcat 的源码,是如何判断这里的 Requet Line 是不是一个包含一个合法的 method name。

顺着异常的类和方法,轻车熟路,直接就能看到了。

if (parsingRequestLinePhase == 2) {
    //
    // Reading the method name
    // Method name is a token
    //
    boolean space = false;
    while (!space) {
        // Read new bytes if needed
        if (byteBuffer.position() >= byteBuffer.limit()) {
            if (!fill(false)) // request line parsing
                return false;
        }
        // Spec says method name is a token followed by a single SP but
        // also be tolerant of multiple SP and/or HT.
        int pos = byteBuffer.position();
        byte chr = byteBuffer.get();
        if (chr == Constants.SP || chr == Constants.HT) {
            space = true;
            request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,
                    pos - parsingRequestLineStart);
        } else if (!HttpParser.isToken(chr)) {
            byteBuffer.position(byteBuffer.position() - 1);
            throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));
        }
    }
    parsingRequestLinePhase = 3;
}

我们注意红色的异常就是上面产生的内容。产生这个是由于读取的byte 不是个 SP 同时下面的 isToken 也不是true导致。

那Token都有谁是怎么定义的?

这里挺有意思的,直接用一个boolean数组来存,前面我们传进来的byte,对应的是这个数组的下标。

public static boolean isToken(int c) {
    // Fast for correct values, slower for incorrect ones
    try {
        return IS_TOKEN[c];
    } catch (ArrayIndexOutOfBoundsException ex) {
        return false;
    }
}

这里的boolean数组,初始化时有几个关联的数组一起,长度为128。

private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE];
private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE];
private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE];
// Control> 0-31, 127
if (i < 32 || i == 127) {
    IS_CONTROL[i] = true;
}
// Separator
if (    i == '(' || i == ')' || i == '<' || i == '>'  || i == '@'  ||
        i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' ||
        i == '/' || i == '[' || i == ']' || i == '?'  || i == '='  ||
        i == '{' || i == '}' || i == ' ' || i == '\t') {
    IS_SEPARATOR[i] = true;
}

// Token: Anything 0-127 that is not a control and not a separator
if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) {
    IS_TOKEN[i] = true;
}

所以这里token的定义明确了,非控制字符,非分隔符,ascii 码小于128 的都是 token。

所以问题产生原因定位了,是由于我们的请求头中传递了「非法」方法名称,导致请求不能正确处理。

我们来看一个正常的请求信息

Request Line 就是上面看到的第一行内容。 GET /a/ HTTP/1.1

那有问题的内容大概是这个样子

谁能从上面解析出来请求方法?

这时你可能会问,正常请求都好好的,你这个怎么搞的?

对。正常没问题,如果我们的Connector 是普通的此时可以响应请求,如果你一直http://localhost:port/a ,可以正常响应,此时后台收到一个https://localhost:port1/a,你要怎么响应?

要知道这两个编码大不一样。所以就出现了本文开头的问题。

如果不想走寻常路,可以自己写个Socket ,连到 Tomcat Server上,发个不合法的请求,大概也是一个样子。

那出现了这类问题怎么排查呢? 别忘了 Tomcat 提供了一系列有用的 Valve ,其中一个查看请求的叫AccessLogValve(阀门(Valve)常打开,快发请求过来 | Tomcat的AccessLogValve介绍)

在 Log里可以查看每个到达的请求来源IP,请求协议,响应状态,请求方法等。但是如果上面的异常产生时,请求方法这类有问题的内容也是拿不到的,此时的response status 是400 。但通过IP我们能看到是谁在一直请求。如果判断是非法请求后,可以再增加我们的过滤Valve,直接将其设置为Deny就OK了。

相关阅读

怎样调试Tomcat源码

从源代码构建Tomcat的方式

怎样阅读源代码?

其他文章:

Tomcat 从眼熟到脸熟

给自己20秒的勇气

怎样回答技术面试题?

觉得本文对你有帮助?请分享给更多人支持一下,谢谢

关注『 Tomcat那些事儿  』 ,发现更多精彩文章!了解各种常见问题背后的原理与答案。深入源码,分析细节,内容原创,欢迎关注。

更多精彩内容:

一台机器上安装多个Tomcat 的原理(回复001)

监控Tomcat中的各种数据 (回复002)

启动Tomcat的安全机制(回复003)

乱码问题的原理及解决方式(回复007)

Tomcat 日志工作原理及配置(回复011)

web.xml 解析实现(回复 012)

线程池的原理( 回复 014)

Tomcat 的集群搭建原理与实现 (回复 015)

类加载器的原理 (回复 016)

类找不到等问题 (回复 017)

代码的热替换实现(回复 018)

Tomcat 进程自动退出问题 (回复 019)

为什么总是返回404? (回复 020)

...

PS: 对于一些 Tomcat常见问题,在公众号的【常见问题】菜单中,有需要的朋友欢迎关注查看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值