BinCat
BinCat
大家好,我是BinCat,一個基于JavaEE API實現的超簡單(不安全的非標準的??,僅用于學習Java容器原理)的Web Server。

Http請求協議解析
Http協議(超文本傳輸協議,HyperText Transfer Protocol)是一種用于分布式、協作式和超媒體信息系統的應用層協議。HTTP是萬維網的數據通信的基礎。要想能夠處理Http請求就必須先解析Http請求,不同的Http請求方式對應的數據包也是不一樣的。
GET請求包示例:
GET / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
POST請求包示例:
POST /?s=java HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Cookie: Hm_lvt_f4c571d9b8811113b4f18e87a6dbe619=1597582351; Hm_lpvt_f4c571d9b889b22224f18e87a6dbe619=1599562693; JSESSIONID=LgxJ127kT7ymIGbC2T1TeipnMP9_2_CqJQjmrqOb
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
id=123&name=admin
解析Http簡要流程
解析POST請求的簡單流程如下(非multipart或chunked請求):
1. 解析第一行的Http協議信息。
2. 解析Http請求Header信息。
3. 解析請求主體(Body)部分。
解析Http請求協議信息
接下來我們將以上述的POST包解析為例簡單的實現Http協議解析。如上POST包,第一行數據中包含了請求方式、請求的URL地址以及Http協議版本信息(空格隔開):POST /?s=java HTTP/1.1。那么我們只需要使用空白符號將字符串切割成數組即可完成解析。
解析Http請求協議示例代碼片段:
// 從Socket中讀取一行數據,讀取請求的URL
String str = dis.readLine();
// 切割請求Http協議信息
String[] strs = str.split("\\s+");
// 解析Http請求方法類型
String method = strs[0];
// 解析Http請求URL地址
String url = strs[1];
// 解析Http請求版本信息
String httpVersion = strs[2];
解析Http請求Header信息
解析完Http請求協議后就應該繼續解析Http Header信息了,Http請求頭從第二行開始到一個空白行結束,Header中的鍵值對以:分割,如下:
Host: localhost:8080
User-Agent: curl/7.64.1
Accept: */*
Content-Length: 17
Content-Type: application/x-www-form-urlencoded
解析Http頭示例代碼片段:
// 創建Header對象
Map<String, String> header = new ConcurrentHashMap<String, String>();
// 解析請求頭信息
while (true) {
// 按行讀取Header頭信息
String line = dis.readLine();
// 當讀取到空行時停止解析Header
if ("".equals(line)) {
break;
}
// 切割Header的Key/Value
String[] headers = line.split(":\\s*", -1);
header.put(headers[0], headers[1]);
}
解析完Header后剩下的也就是最后的Http請求主體部分了,瀏覽器會將請求的參數以&為連接符拼接出多個參數,參數名稱和參數值以=分割,并且參數值默認會使用URL編碼,如下:
id=123&name=admin
解析body中的請求參數時需要先從Header中讀取請求的主體大小,即:Content-Length,因為body中允許出現換行\n等特殊內容,所以解析body時應該按字節讀取數據。除此之外,解析Body中的請求參數之前應該先解析URL中的請求參數,即GET傳參部分:/?s=java,然后再解析body中的參數。
解析Http GET參數代碼片段:
// 解析GET請求參數
if (url.contains("?")) {
String[] parameterStrs = url.split("\\?");
this.requestURL = parameterStrs[0];
// 初始化Http請求的QueryString
this.queryString = parameterStrs[1];
// 按"&"切割GET請求的參數
String[] parameters = queryString.split("&");
// 解析GET請求參數
for (String parameter : parameters) {
String[] tmp = parameter.split("=", -1);
if (tmp.length == 2) {
parameterMap.put(tmp[0], new String[]{URLDecoder.decode(tmp[1])});
}
}
}
Cookie解析
Cookie是非常Http請求中非常重要的用戶憑證,Cookie位于請求頭中的cookie字段,多個Cookie以;分割,Cookie的參數和參數值以=切分。Cookie中會存儲一個叫JSESSIONID(Java標準容器中叫JSESSIONID),用于識別服務器端存儲的用戶會話信息。
示例Cookie:
Cookie: Hm_lvt_f4c571d9b8811113b4f18e87a6dbe619=1597582351; Hm_lpvt_f4c571d9b889b22224f18e87a6dbe619=1599562693; JSESSIONID=LgxJ127kT7ymIGbC2T1TeipnMP9_2_CqJQjmrqOb
示例Cookie解析代碼片段:
// 解析Cookie
if (headerMap.containsKey("cookie")) {
// 切分Cookie字符串
String[] cookies = headerMap.get("cookie").split(";\\s+", -1);
// 初始化Cookie數組長度
this.cookie = new Cookie[cookies.length];
for (int i = 0; i < cookies.length; i++) {
String cookieStr = cookies[i];
String[] tmp = cookieStr.split("=", -1);
if (tmp.length == 2) {
// 創建Cookie對象
this.cookie[i] = new Cookie(tmp[0], URLDecoder.decode(tmp[1]));
}
}
}
解析Http請求主體
解析Http主體代碼片段:
if ("POST".equalsIgnoreCase(method)) {
String contentType = header.get("Content-Type");
// 解析POST請求參數
if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
// 獲取請求的主體長度
int contentLength = Integer.parseInt(header.get("Content-Length"));
// 創建一個和請求體一樣大小的緩沖區
byte[] bytes = new byte[contentLength];
// 讀取POST主體內容
dis.read(bytes);
// 解析POST請求內容
String body = new String(bytes, "ISO8859-1");
// 按"&"切割POST請求的參數
String[] parameters = body.split("&");
// 解析POST請求參數
for (String parameter : parameters) {
String[] tmp = parameter.split("=", -1);
if (tmp.length == 2) {
parameterMap.put(tmp[0], URLDecoder.decode(tmp[1]));
}
}
}
}
Java Web安全
推薦文章: