本次以GET請求為例,來分析下Tomcat對請求參數(shù)的處理方式。
說起請求參數(shù),首先我們得了解下,請求參數(shù)的定義。我們一般的請求形式類似下面的樣子:
http://hostName:port/contextName/query?parameName1=paramValue1¶meName2=paramValue2
參照上面的請求格式,url請求中帶參數(shù)的形式(即我們常說的GET請求),是在請求目標后以問號開始,后面是參數(shù)名值對,多個名值對間以和號(&)分隔。
以下是HTTP規(guī)范RFC2616中對此的定義:
通過URL傳遞的參數(shù),在Tomcat中是怎么解析出來的呢?
我們一般在Servlet中要獲取某個參數(shù),一般通過如下的方式
String value = request.getParameter("paramName");
我們在需要的時候通過參數(shù)名直接取,這個值又是什么設置的?名值對又是如何對應起來的?
我們順著getParameter方法這個藤,來摸摸實現(xiàn)這個瓜。
我們在使用HttpServletRequest這個對象時,其實一直在使用的是其一個門面對象(RequestFacade),此對象使用了設計模式中的門面模式,封裝了HttpServletRequest中的一些細節(jié),只暴露出一些必要的API。
getParameter方法的代碼是下面這個樣子:
/ * Return the value of the specified request parameter, if any; otherwise,
- return <code>null</code>. If there is more than one value defined,
- return only the first one.
- @param name Name of the desired request parameter
*/
public String getParameter(String name) {
if (!parametersParsed) {
parseParameters();
}
return coyoteRequest.getParameters().getParameter(name);
}
每次請求時,會先判斷參數(shù)是否已經解析過,如果已經解析過就直接返回。
protected void parseParameters() {
parametersParsed = true; //注意這里,解析之后就設為true了。
Parameters parameters = coyoteRequest.getParameters();
boolean success = false;
try {
// Set this every time in case limit has been changed via JMX
parameters.setLimit(getConnector().getMaxParameterCount());
}
...
parameters.handleQueryParameters();
}
所以,這個名值對的配置,初始化,是發(fā)生在第一次調用getParameter方法時。
再向下,這個handleQueryParameters是具體處理的方法。這里我們假設請求如下url:
http://localhost:8080/test?abc=1&def=2
在handleQueryParameters方法中,我們通過debug界面觀察到如下內容:
此處parameters包含一個屬性queryMB,其值剛好是我們傳進來的字符串。所以后面的參數(shù)處理,是基于這個屬性進行的。
再之后,在Parameter這個類的processParameter方法中,我們看到的是這樣樣子的處理方式:
我們看到,基本是遍歷字符串中的各個char,遇到特定字符=和&之后,再從各個index獲取等號前后的名和值。
中間特別的一個地方是,遇到%和+時,是出現(xiàn)了像漢字一類的,其實是需要轉義的,所以處理也是在此進行的。
解析后,名值對是存放在ArrayList這樣一個數(shù)據(jù)結構中??聪旅娴拇a,
public void addParameter( String key, String value ) {
ArrayList<String> values = paramHashValues.get(key);
if (values == null) {
values = new ArrayList<>(1);
paramHashValues.put(key, values);
}
values.add(value);
}
是執(zhí)行完上面的方法后,代碼向下執(zhí)行,看到的parameters這個對象,值已經變成了這樣:
abc=1,\n def=2,\n
注意上面代碼標紅加粗的這兩行,
你是否還記得上面提到,如果多個參數(shù),對于重名的只返回第一個符合的項這件事?
具體request的參數(shù)請求中,如果不涉及初次處理,那執(zhí)行的是下面的代碼,很簡單,就是直接從Map里取對應key的ArrayList,有值的話就從中取第一個值。
public String getParameter(String name ) {
handleQueryParameters();
ArrayList<String> values = paramHashValues.get(name);
if (values != null) {
if(values.size() == 0) {
return "";
}
return values.get(0); //注意這里,就是在兌現(xiàn)只返回第一個的承諾!!!
} else {
return null;
}
}
和我們直觀理解上基本一致,我們深入代碼,更多的是一起看一些細節(jié)上的東西,比如重名時返回第一項,比如實現(xiàn)過程中使用的數(shù)據(jù)結構等。
將本號推薦給你更多的朋友,大家一起交流。