springboot项目在服务器上运行一段时间后,可能会报出这样一个异常。

java.lang.IllegalArgumentException: Invalid character found in method name [0x160x030x010x020x000x010x000x010xfc0x030x030x920xff0xdbN0xb40x890xa8q0x9d0x1c0xde0x0dZ0xb6:0xb00xbe0xd8_0x850xb10x950xeeB0xbbk0xdb0xf00xd60xeb0xe0u]. HTTP method names must be tokens
        at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:417) ~[tomcat-embed-core-9.0.41.jar!/:9.0.41]

这是从Http11InputBuffer里抛出的一个异常,这个类是tomcat-enbed-core-9.0.41-source里的。研究了一下 还是有些心得的。首先是在417这一行抛出的异常,(这是阶段2,检查http请求方式是否有非法字符,类中用这个变量表示parsingRequestLinePhase),就是下面代码的最后一行抛出

int pos = byteBuffer.position();
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)) {
    // Avoid unknown protocol triggering an additional error
    request.protocol().setString(Constants.HTTP_11);
    String invalidMethodValue = parseInvalid(parsingRequestLineStart, byteBuffer);
    throw new IllegalArgumentException(sm.getString("iib.invalidmethod", invalidMethodValue));

这一部分代码解析的是http头部信息,头部信息长这个样子

GET / HTTP/1.1
Host: www.enjoytoday.cn
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://www.enjoytoday.cn/posts/326
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: bdshare_firstime=1466032270994; UM_distinctid=15c4ef2ac4e2e4-0d13269271b947-1b2a120b-1fa400-15c4ef2ac4f7b5; un=aGZjYWk=; comment_author=aGZjYWk=; comment_author_email=1710600212@qq.com; comment_author_url=http://www.enjoytoday.cn; c_id=dUhIaTlndmc4MVVYbjRQTGxMRTotMTpFODg3QjgzQjg1NjgxQjQxRUYxNjg2QzJFRkMyQjI2QQ==; JSESSIONID=ADBC8C3DADF6C815D778450C193C6637.ajp13_worker; Hm_lvt_ce55bfda158556585a8b7b246346c8ba=1498560244,1498739070,1498833193,1498917432; Hm_lpvt_ce55bfda158556585a8b7b246346c8ba=1498917597; CNZZDATA1262047894=1598545996-1495973145-%7C1498917578
 
username=hfcai&sex=man

所以如果是一个Get请求 那么此时buffer中头三个就是 71 69 84,如下图所示。
在这里插入图片描述
然后就逐个读取,等于空格或者是水平制表符的时候,就跳出。不等于的时候进入HttpParser的isToken方法检验。HttpParser中有若干个boolean数组,每个数组大小都是128。下面是他的初始化数组.

static {
        for (int i = 0; i < ARRAY_SIZE; i++) {
            // Control> 0-31, 127
            if (i < 32 || i == 127) {
                IS_CONTROL[i] = true;
            }
            //i小于32或者等于128 IS_CONTROL对应位置为真。
            // 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;
            }
            //i为分隔符时,对应位置为真
            
            
            // 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;
            }
            //不是控制符,也不是分隔符,且小于128,对应位置为真
            
            。。。
        }
            
 public static boolean isToken(int c) {
        // Fast for correct values, slower for incorrect ones
        try {
            return IS_TOKEN[c];
        } catch (ArrayIndexOutOfBoundsException ex) {
            return false;
        }
    }

也就是说,这个异常的最终原因是因为http 请求方式中,也就是GET Post中,包含了非法字符。接下来试一试捕获这个异常。postman会检查http请求方式,不太好用,请求方法中含有非法字符的请求发不出去。最终发现在idea中Tools-HTTPClients可以发出这种请求。
在这里插入图片描述
抛出的异常如下,可以从HttpInputBuffer的417行抛出,可以看出来跟上面是同一个异常。

java.lang.IllegalArgumentException: Invalid character found in method name [GET[]. HTTP method names must be tokens
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:417) ~[tomcat-embed-core-9.0.41.jar:9.0.41]

如果实在http11InputBuffer533行报的错,那很明显就是在解析 GET / HTTP/1.1 其中的 HTTP/1.1 时报的错,可以看到 532行是HttpParser.isHttpProtocal(chr), 点开httpParser

static{
...
if (i == 'H' || i == 'T' || i == 'P' || i == '/' || i == '.' || (i >= '0' && i <= '9')) {
               IS_HTTP_PROTOCOL[i] = true;
           }
...
}

public static boolean isHttpProtocol(int c) {
       // Fast for valid HTTP protocol characters, slower for some incorrect
       // ones
       try {
           return IS_HTTP_PROTOCOL[c];
       } catch (ArrayIndexOutOfBoundsException ex) {
           return false;
       }
   }

也就是说,这个位置只要不是H, T , P , / , . 或者数字,那就都会在533行报错。

关于解决方案:
1、 尝试用controller统一处理,失败,代码如下


@ControllerAdvice
public abstract class BaseController {

    @ExceptionHandler({IllegalArgumentException.class })
    public String parseHttpException(HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<>();
        map.put("status", "-1001");
        map.put("message", "http解析错误");
        System.out.println(map.toString());
        return null;
    }
}

结果就是拦截不到这个异常,该抛出还是抛出了。
2、尝试用aop,织入http11inputbuffer,代码如下

public class CatchIllegalHttpMethodAdvice {

    //匹配Http11InputBuffer抛出的异常
    @Pointcut("execution(boolean org.apache.coyote.http11.Http11InputBuffer.*(..))")
    public void phase2Exception(){}

    @AfterThrowing(value = "phase2Exception()",throwing = "e")
    public void handlePhase2Exception(JoinPoint joinPoint,RuntimeException e){
        System.out.println("========================================");
        System.out.println(String.valueOf(e.getMessage()));
    }
}

还是拦截不到。。。
暂时想不到别的办法了,大家有什么想法的话可以写在评论区。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐