一个有意思的Tomcat 异常

开发 开发工具
在公众号后台,经常能看到读者的消息,其中一部分消息是关于Tomcat使用过程中遇到的问题。今天我们就聊聊关于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) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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

顺着异常信息我们往上看,首先这个提示是解析请求头出现的错误。更细节一些是解析请求头中第一行,所谓的「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

  • 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.

我们注意红色的异常就是上面产生的内容。产生这个是由于读取的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; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

这里的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; 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

所以这里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了。

【本文为51CTO专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】

戳这里,看该作者更多好文

责任编辑:赵宁宁 来源: 51CTO专栏
相关推荐

2020-12-12 13:50:16

云开发

2021-01-27 13:54:05

开发云原生工具

2023-05-15 09:16:18

CSSCSS Mask

2024-05-20 01:10:00

Promise变量

2020-03-10 14:59:16

oracle数据库监听异常

2009-08-26 17:53:31

C# DropDown

2022-03-21 10:21:50

jQuery代码模式

2021-03-25 06:12:55

SVG 滤镜CSS

2015-03-12 10:46:30

代码代码犯罪

2012-05-22 10:12:59

jQuery

2024-03-18 08:14:07

SpringDAOAppConfig

2022-08-15 22:34:47

Overflow方向裁切

2022-07-11 13:09:26

mmapLinux

2022-06-15 07:21:47

鼠标指针交互效果CSS

2022-05-20 07:36:02

LiveTerm工具

2021-02-20 16:01:26

Github前端开发

2017-08-01 00:52:07

kafka大数据消息总线

2013-08-28 09:46:09

Debian LinuLinux发行版

2012-06-19 16:49:19

Web开发

2021-04-23 07:51:56

CSS Container Q Chrome
点赞
收藏

51CTO技术栈公众号