自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一個(gè)有意思的Tomcat 異常

開(kāi)發(fā) 開(kāi)發(fā)工具
在公眾號(hào)后臺(tái),經(jīng)常能看到讀者的消息,其中一部分消息是關(guān)于Tomcat使用過(guò)程中遇到的問(wèn)題。今天我們就聊聊關(guān)于Tomcat 異常的問(wèn)題。

在公眾號(hào)后臺(tái),經(jīng)常能看到讀者的消息,其中一部分消息是關(guān)于Tomcat使用過(guò)程中遇到的問(wèn)題。但是,由于微信的「克制」,如果消息回復(fù)的比較晚,就會(huì)遇到「過(guò)期」的尷尬,我并不能主動(dòng)聯(lián)系到提問(wèn)的人。

后面有需要討論問(wèn)題的朋友,如果公眾號(hào)發(fā)消息未收到回復(fù),可以加我微信。

說(shuō)回正題,之前有位讀者留言,說(shuō)了一個(gè) Tomcat 異常的問(wèn)題。

即 Tomcat 各功能正常,不影響使用,但是偶爾的在日志中會(huì)看到類似于這樣的異常信息:

  1. INFO [https-apr-8443-exec-5] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header 
  2.  Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level. 
  3.  java.lang.IllegalArgumentException: Invalid character (CR or LF) found in method name 
  4.     at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:443) 
  5.     at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:982) 

為啥報(bào)這個(gè)呢?明明自己沒(méi)做什么操作。

順著異常信息我們往上看,首先這個(gè)提示是解析請(qǐng)求頭出現(xiàn)的錯(cuò)誤。更細(xì)節(jié)一些是解析請(qǐng)求頭中第一行,所謂的「Request Line」的時(shí)候出了問(wèn)題。

什么是「Request Line」呢? 就是HTTP 規(guī)范中指定的,以請(qǐng)求方法開(kāi)頭 再加上請(qǐng)求URI 等。具體看這個(gè)規(guī)范說(shuō)明

這里我們的異常信息提示我們是在解析 Method name的時(shí)候出了問(wèn)題??匆?guī)范里說(shuō)了「The Request-Line begins with a method token」也就是有固定的東西的,不是啥都能叫一個(gè)method name。我們熟悉的GET/POST/PUT/DELETE都是這里允許的。

我們?cè)賮?lái)看 Tomcat 的源碼,是如何判斷這里的 Requet Line 是不是一個(gè)包含一個(gè)合法的 method name。

順著異常的類和方法,輕車熟路,直接就能看到了。

  1. if (parsingRequestLinePhase == 2) { 
  2.     // 
  3.     // Reading the method name 
  4.     // Method name is a token 
  5.     // 
  6.     boolean space = false
  7.     while (!space) { 
  8.         // Read new bytes if needed 
  9.         if (byteBuffer.position() >= byteBuffer.limit()) { 
  10.             if (!fill(false)) // request line parsing 
  11.                 return false; 
  12.         } 
  13.         // Spec says method name is a token followed by a single SP but 
  14.         // also be tolerant of multiple SP and/or HT. 
  15.         int pos = byteBuffer.position(); 
  16.         byte chr = byteBuffer.get(); 
  17.         if (chr == Constants.SP || chr == Constants.HT) { 
  18.             space = true
  19.             request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, 
  20.                     pos - parsingRequestLineStart); 
  21.         } else if (!HttpParser.isToken(chr)) { 
  22.             byteBuffer.position(byteBuffer.position() - 1); 
  23.             throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); 
  24.         } 
  25.     } 
  26.     parsingRequestLinePhase = 3

我們注意紅色的異常就是上面產(chǎn)生的內(nèi)容。產(chǎn)生這個(gè)是由于讀取的byte 不是個(gè) SP 同時(shí)下面的 isToken 也不是true導(dǎo)致。

那Token都有誰(shuí)是怎么定義的?

這里挺有意思的,直接用一個(gè)boolean數(shù)組來(lái)存,前面我們傳進(jìn)來(lái)的byte,對(duì)應(yīng)的是這個(gè)數(shù)組的下標(biāo)。

  1. public static boolean isToken(int c) { 
  2.     // Fast for correct values, slower for incorrect ones 
  3.     try { 
  4.         return IS_TOKEN[c]; 
  5.     } catch (ArrayIndexOutOfBoundsException ex) { 
  6.         return false; 
  7.     } 

這里的boolean數(shù)組,初始化時(shí)有幾個(gè)關(guān)聯(lián)的數(shù)組一起,長(zhǎng)度為128。

  1. private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE]; 
  2. private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; 
  3. private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; 
  4. // Control> 0-31, 127 
  5. if (i < 32 || i == 127) { 
  6.     IS_CONTROL[i] = true; 
  7. // Separator 
  8. if (    i == '(' || i == ')' || i == '<' || i == '>'  || i == '@'  || 
  9.         i == ',' || i == ';' || i == ':' || i == '\\' || i == '\"' || 
  10.         i == '/' || i == '[' || i == ']' || i == '?'  || i == '='  || 
  11.         i == '{' || i == '}' || i == ' ' || i == '\t') { 
  12.     IS_SEPARATOR[i] = true; 
  13.  
  14. // Token: Anything 0-127 that is not a control and not a separator 
  15. if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) { 
  16.     IS_TOKEN[i] = true; 

所以這里token的定義明確了,非控制字符,非分隔符,ascii 碼小于128 的都是 token。

所以問(wèn)題產(chǎn)生原因定位了,是由于我們的請(qǐng)求頭中傳遞了「非法」方法名稱,導(dǎo)致請(qǐng)求不能正確處理。

我們來(lái)看一個(gè)正常的請(qǐng)求信息

Request Line 就是上面看到的第一行內(nèi)容。 GET /a/ HTTP/1.1

那有問(wèn)題的內(nèi)容大概是這個(gè)樣子

誰(shuí)能從上面解析出來(lái)請(qǐng)求方法?

這時(shí)你可能會(huì)問(wèn),正常請(qǐng)求都好好的,你這個(gè)怎么搞的?

對(duì)。正常沒(méi)問(wèn)題,如果我們的Connector 是普通的此時(shí)可以響應(yīng)請(qǐng)求,如果你一直http://localhost:port/a ,可以正常響應(yīng),此時(shí)后臺(tái)收到一個(gè)https://localhost:port1/a,你要怎么響應(yīng)?

要知道這兩個(gè)編碼大不一樣。所以就出現(xiàn)了本文開(kāi)頭的問(wèn)題。

如果不想走尋常路,可以自己寫(xiě)個(gè)Socket ,連到 Tomcat Server上,發(fā)個(gè)不合法的請(qǐng)求,大概也是一個(gè)樣子。

那出現(xiàn)了這類問(wèn)題怎么排查呢? 別忘了 Tomcat 提供了一系列有用的 Valve ,其中一個(gè)查看請(qǐng)求的叫AccessLogValve(閥門(Valve)常打開(kāi),快發(fā)請(qǐng)求過(guò)來(lái) | Tomcat的AccessLogValve介紹)

在 Log里可以查看每個(gè)到達(dá)的請(qǐng)求來(lái)源IP,請(qǐng)求協(xié)議,響應(yīng)狀態(tài),請(qǐng)求方法等。但是如果上面的異常產(chǎn)生時(shí),請(qǐng)求方法這類有問(wèn)題的內(nèi)容也是拿不到的,此時(shí)的response status 是400 。但通過(guò)IP我們能看到是誰(shuí)在一直請(qǐng)求。如果判斷是非法請(qǐng)求后,可以再增加我們的過(guò)濾Valve,直接將其設(shè)置為Deny就OK了。

【本文為51CTO專欄作者“侯樹(shù)成”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)『Tomcat那些事兒』獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來(lái)源: 51CTO專欄
相關(guān)推薦

2020-12-12 13:50:16

云開(kāi)發(fā)

2021-01-27 13:54:05

開(kāi)發(fā)云原生工具

2023-05-15 09:16:18

CSSCSS Mask

2024-05-20 01:10:00

Promise變量

2020-03-10 14:59:16

oracle數(shù)據(jù)庫(kù)監(jiān)聽(tīng)異常

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-06-15 07:21:47

鼠標(biāo)指針交互效果CSS

2022-08-15 22:34:47

Overflow方向裁切

2021-02-20 16:01:26

Github前端開(kāi)發(fā)

2022-07-11 13:09:26

mmapLinux

2022-05-20 07:36:02

LiveTerm工具

2017-08-01 00:52:07

kafka大數(shù)據(jù)消息總線

2012-06-19 16:49:19

Web開(kāi)發(fā)

2021-04-23 07:51:56

CSS Container Q Chrome

2013-08-28 09:46:09

Debian LinuLinux發(fā)行版
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)