Try   HackMD

Oh~ Http的頭裡有蛇


情境

Note:
要做的事是透過前端從 serverA取資料,用 content-type FormData格式打包成一個 http post方式的 request送到 serverB。遇到的問題是,B無法讀取接收到的訊息。但在這之前也做過一樣方向的傳輸


Note:
只是傳輸的訊息格式不同,剛剛是傳FormData, 第一次是傳 application/json格式, B接收之後順利解讀。所以猜測是B的解讀方式有誤,從這個方向去debug


聊聊錯誤示範起手式
  • 盲測
    • 後端換各種接法:
      1. request.form['path']
      2. request.form.get('path',False)
      3. request.data
      4. request.json
      5. request.get_json()

Note:
因此就沒有大腦的測試各種接收方式,結果都讀不到。


Why 後端B各種嘗試還是無法解析 http request的 data

Note:
卡了一個上午想不到還可以怎麼做,於是開始求救


  • 往上游看(ES大大):
    • multipart/form-data 長得不一樣

Note:
謝謝ES幫忙,ES從源頭,發送訊息的 serverA開始追蹤,並試著傳另一個已經確保可行的相同的content-type格式的訊息,很快地就發現問題。開啟瀏覽器開發介面的network tag,左邊是 serverB不能解析的訊息,右邊是可以解析的,可以看到上方兩個紅色框的差別是右邊有 boundary字串;下方結果的差別是右邊有 view parsed的 tab選項。 然後就很驚訝原來一開始 serverA打包訊息的方式就不對了。


Content-type 是什麼

Note:
用於指示資源的媒體類型。在request中, 用在client告訴 server他傳送的訊息是什麼類型的data。在respose中則反之。

Multipurpose Internet Mail Extensions (MIME)
是一種Internet標準,它擴展了電子郵件的格式以支持

  1. ASCII以外的字符集中的文本
  2. 非文本附件:音頻,視頻,圖像,應用程序等。
  3. 具有多個部分的消息正文
  4. 非ASCII字符集中的標頭信息

雖然MIME主要是為SMTP設計的 但MIME標准定義的內容類型在電子郵件之外的通信協議中也很重要,例如HTTP。 服務器在任何Web傳輸的開頭插入MIME頭。


content-type:

    • text/html; charset=utf-8
    • text/plain;
    • text/css
    • image/png
    • image/jpeg
    • audio/mpeg
    • audio/ogg
    • video/quicktime
    • video/mp4
    • application/octet-stream
    • application/x-www-form-urlencoded;
    • application/json
    • application/xml
  1. multipart

    1. multipart/form-data

Note:
這邊是幾個常見的類型/次類型。 我們這次遇到的問題格式是multipart。顧名思義就是data有很多個part,可能很大。當文件太長,HTTP無法在一個包之內發送完畢,就需要分割數據,分割成一個一個塊發送給服務端。


Multipart

需要有 boundary來封裝multi資料

Note:
為了一個一個塊發送給服務端,multipart需要有 boundary來區分每一塊。


有正確的封裝後端才能解析

Note:
對於 multipart entities,需要使用邊界指令,該指令由已知通過電子郵件網關非常 robust一組字符中的1到70個字符組成,並且不以空格結尾。 它用於封裝message的多個部分的邊界。 通常,header邊界前面有兩個破折號,最後的邊界在末尾附加了兩個破折號。


假如正確地設好 boundary

後端(python)解析方式:

  • 以 multipart/form-data為例:
    • request.form.get('path',False)
    • request.form['path']

補充後端(python)對於其他常見的content-type的解析方式

  • application/x-www-form-urlencoded:
    ​​​​f = request.form ​​​​for key in f.keys(): ​​​​ for value in f.getlist(key): ​​​​ print(key,":",value)
  • application/json:
    ​​​​request.get_json() recv: {'encodedData': 'testjson'} ​​​​request.json recv: {'encodedData': 'testjson'} ​​​​request.data recv: b'{"encodedData":"testjson"}' #bytes, need "json.loads()"

How to set the boundary?



var data = { name: 'theKey', surname: 'theValue' }; var boundary = String(Math.random()).slice(2); var boundaryMiddle = '--' + boundary + '\r\n'; var boundaryLast = '--' + boundary + '--\r\n' var body = ['\r\n']; for (var key in data) { body.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + data[key] + '\r\n'); } body = body.join(boundaryMiddle) + boundaryLast; var xhr = new XMLHttpRequest(); xhr.open('POST', '/test', true); xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); xhr.onreadystatechange = function() { if (this.readyState != 4) return; alert( this.responseText ); } xhr.send(body);

阿可以不用自己設拉其實

var FD = new FormData(); var xhr = new XMLHttpRequest(); xhr.open('POST', '/test', true); xhr.send(FD);

那..什麼時候要DIY

確保傳到後端是自己想像的格式


舉例: 如果要傳 json物件xhr.send(JSON.stringify({"t1":"t1data"}));

  • 沒有設 Header時, javascript的預設的 Content-type會是 text/plain

只能用 request.data取出 bytes字串

  • 若自己設 content-type為 application/json,後端可以直接取得 json物件, 方便得到key和value
request.get_json() recv: {'encodedData': 'testjson'}
request.json       recv: {'encodedData': 'testjson'}
request.data       recv: b'{"encodedData":"testjson"}'

狀況2:後端B可以解析 request的 data了,不過這不是肯德G


竟然收到 string: [object ArrayBuffer]


怎麼會現在才發現?


debug msg下得不夠確實

Note:
在前端先印出來看,發現前端讀到的data就已經不是預想的類型了。serverA送出的是 base64轉換過的 bytes obj,原本以為前端收到後可以直接轉傳看來案情並不單純


Why base64的 bytes obj變 ArrayBuffer

Javascript中的 Buffers和 Views

Note:
謝謝Chris幫忙,請教chris後了解, javascipt會將 data存放在 buffer,然後用不同的views去解讀,會讀出不同的東西。因為解讀的長度單位不同,像是 base64的 bytes obj 就要用1個 byte的單位去解讀,出來才是我們要的資料


將收到的 data用 Uint8解讀出來後再往下傳

var origin_string = String.fromCharCode.apply(null, new Uint8Array(data));


Oh, by the way


好像只有莎拉不知道的小技巧0.0


Thank U