1. 程式人生 > >http協議之請求方法、請求頭、請求體分析和Netty解析

http協議之請求方法、請求頭、請求體分析和Netty解析

請求報文

Http請求報文由三部分組成:請求行,請求頭,請求體

攜帶資訊
請求行:請求方法、請求地址、協議名稱和版本號
請求頭:Referer、User-Agent、Accept、Cookie、Cache-Control、Content-Length等屬性。Content-Length可用於服務端判斷訊息接受完的條件
請求體:GET請求與POST請求傳遞方式不同(Message Body)

request line 和每個 header 各佔一行,以換行符 CRLF(即 \r\n)分割
GET請求體

傳輸資料有限,因為瀏覽器對URL的長度有限制。拼接在URL中,但這種方式也適用於POST方法。比如微信的語音識別介面…

POST:http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN

響應報文

Http響應報文由三部分組成:響應行,響應頭,響應體

攜帶資訊
響應行:報文協議及版本,狀態碼及狀態描述
響應頭:Referer、User-Agent、Accept、Cookie、Cache-Control等屬性
響應體:伺服器返回的資料
響應狀態碼
1xx 訊息,一般是告訴客戶端,請求已經收到了,正在處理,別急...
2xx 處理成功,一般表示:請求收悉、我明白你要的、請求已受理、已經處理完成等資訊.
3xx 重定向到其它地方。它讓客戶端再發起一個請求以完成整個處理。
4xx 處理髮生錯誤,責任在客戶端,如客戶端的請求一個不存在的資源,客戶端未被授權,禁止訪問等。
5xx 處理髮生錯誤,責任在服務端,如服務端丟擲異常,路由出錯,HTTP版本不支援等。
POST 請求體型別分類
  • form-data:就是http請求中的multipart/form-data,它會將表單的資料處理為一條訊息,以標籤為單元,用分隔符分開。既可以上傳鍵值對,也可以上傳檔案。當上傳的欄位是檔案時,會有Content-Type來表名檔案型別;content-disposition,用來說明欄位的一些資訊;由於有boundary隔離,所以multipart/form-data既可以上傳檔案,也可以上傳鍵值對,它採用了鍵值對的方式,所以可以上傳多個檔案。可以模擬填寫表單,並且提交表單。可以選擇檔案型別,但不能儲存歷史記錄
  • x-www-form-urlencoded:就是application/x-www-from-urlencoded,會將表單內的資料轉換為鍵值對
  • raw:原生任意格式的文字,text、json、xml、html
  • binary:相當於Content-Type:application/octet-stream,從字面意思得知,只可以上傳二進位制資料,通常用來上傳檔案,由於沒有鍵值,所以,一次只能上傳一個檔案。image, audio or video files.text files,也不能儲存歷史記錄,每次選擇檔案,提交
POSTMan

pre-request script和test scripts一樣,都是javascript,同時還支援表示式,可以用兩個{{}}訪問環境變數。

變數設定

image

# Pre-request Script呼叫
var temp = parseInt(postman.getGlobalVariable("xhbxId"));
temp += 1;
postman.setGlobalVariable("xhbxId", temp);

# 響應Tests
var temp = postman.getGlobalVariable("xhbxId");
tests["Body matches string"] = responseBody.has("\"_id\":\""+temp+"\"");

# 測試結果會展示在Test Results中

image

PostMan請求測試流程

image

Netty解析Http請求

GET請求
HttpRequest request = (HttpRequest) msg;
String uri = request.uri();

// 用瀏覽器發起 HTTP 請求時,常常會被 uri = "/favicon.ico" 所幹擾
if(uri.equals(FAVICON_ICO)){
    return;
}

// 吧URI分割成key-value形式
QueryStringDecoder decoder = new QueryStringDecoder(uriString);  
Map<String, List<String>> parame = decoder.parameters();  
Iterator<Entry<String, List<String>>> iterator = parame.entrySet().iterator();
while(iterator.hasNext()){
    Entry<String, List<String>> next = iterator.next();
    parmMap.put(next.getKey(), next.getValue().get(0));
}
POST請求
1、解析 application/json
FullHttpRequest fullRequest = (FullHttpRequest) msg;
String jsonStr = fullRequest.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
JSONObject obj = JSON.parseObject(jsonStr);
for(Entry<String, Object> item : obj.entrySet()){
  System.out.println(item.getKey()+"="+item.getValue().toString());
}
2、解析 application/x-www-form-urlencoded
方法一:
FullHttpRequest fullRequest = (FullHttpRequest) msg;
String jsonStr = fullRequest.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
QueryStringDecoder queryDecoder = new QueryStringDecoder(jsonStr, false);
Map<String, List<String>> uriAttributes = queryDecoder.parameters();
for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
  for (String attrVal : attr.getValue()) {
        System.out.println(attr.getKey()+"="+attrVal);
    }
}

方法二:
HttpRequest request = (HttpRequest) msg;
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request, Charsets.toCharset(CharEncoding.UTF_8));
List<InterfaceHttpData> datas = decoder.getBodyHttpDatas();
for (InterfaceHttpData data : datas) {
  if(data.getHttpDataType() == HttpDataType.Attribute) {
    Attribute attribute = (Attribute) data;
    System.out.println(attribute.getName() + "=" + attribute.getValue());
  }
}
3、解析 multipart/form-data (檔案上傳)
DiskFileUpload.baseDirectory = "/data/fileupload/";
HttpRequest request = (HttpRequest) msg;
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request, Charsets.toCharset(CharEncoding.UTF_8));
List<InterfaceHttpData> datas = decoder.getBodyHttpDatas();
for (InterfaceHttpData data : datas) {
  if(data.getHttpDataType() == HttpDataType.FileUpload) {
    FileUpload fileUpload = (FileUpload) data;
    String fileName = fileUpload.getFilename();
    if(fileUpload.isCompleted()) {
      //儲存到磁碟
      StringBuffer fileNameBuf = new StringBuffer();
      fileNameBuf.append(DiskFileUpload.baseDirectory).append(fileName);
      fileUpload.renameTo(new File(fileNameBuf.toString()));
    }
  }
}
自定義 HTTP POST 的 message body 解碼器

如果你要實現一個頂層解碼器,就要繼承 MessageToMessageDecoder 並重寫其 decode 方法。

MessageToMessageDecoder 繼承了 ChannelHandlerAdapter,也就是說解碼器其實就是一個 handler,只不過是專門用來做解碼的事情。

記憶體洩漏
netty 提供了記憶體洩漏的監測機制,預設就會從分配的 ByteBuf 裡抽樣出大約 1% 的來進行跟蹤

-Dio.netty.leakDetectionLevel=advanced

禁用(DISABLED) - 完全禁止洩露檢測,省點消耗
簡單(SIMPLE) - 預設等級,告訴我們取樣的 1% 的 ByteBuf 是否發生了洩露,但總共一次只打印一次,看不到就沒有了。
高階(ADVANCED) - 告訴我們取樣的 1% 的 ByteBuf 發生洩露的地方。每種型別的洩漏(建立的地方與訪問路徑一致)只打印一次。
偏執(PARANOID) - 跟高階選項類似,但此選項檢測所有 ByteBuf,而不僅僅是取樣的那 1%。在高壓力測試時,對效能有明顯影響。
HTTP什麼情況下會關閉連結
1、使用訊息頭部 Content-Length判斷

2、使用訊息首部欄位 Transfer-Encoding判斷
即 chunked 編碼傳輸