--- title: 莎拉抓蟲記之全身埋在HTTP request裡 tags: python, base64, http request --- ## Oh~ Http的頭裡有蛇 --- ### 情境 <!-- .slide: data-background="#FFFFFF" data-transition="zoom-in zoom-out" data-transition-speed="slow" --> ![](https://i.imgur.com/auzw9DW.png =x350) Note: 要做的事是透過前端從 serverA取資料,用 content-type FormData格式打包成一個 http post方式的 request送到 serverB。遇到的問題是,B無法讀取接收到的訊息。但在這之前也做過一樣方向的傳輸 ---- <!-- .slide: data-background="#FFFFFF" data-transition="zoom-in zoom-out" data-transition-speed="slow" --> ![](https://i.imgur.com/SJAJX0l.png) 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 長得不一樣 ![](https://i.imgur.com/denvkab.jpg) <!-- .slide: data-transition="fade-in convex-out" --> 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標準,它擴展了電子郵件的格式以支持 <!-- 瀏覽器在某些情況下會進行MIME探察,並不一定會遵循此標頭的值; 為了防止這種行為,Header中有 X-Content-Type-Options可以設置為nosniff。 --> 1. ASCII以外的字符集中的文本 2. 非文本附件:音頻,視頻,圖像,應用程序等。 3. 具有多個部分的消息正文 4. 非ASCII字符集中的標頭信息 <!-- 1. Text in character sets other than ASCII 2. Non-text attachments: audio, video, images, application programs etc. 3. Message bodies with multiple parts 4. Header information in non-ASCII character sets --> 雖然MIME主要是為SMTP設計的 但MIME標准定義的內容類型在電子郵件之外的通信協議中也很重要,例如HTTP。 服務器在任何Web傳輸的開頭插入MIME頭。 ---- #### content-type: 1. * text/html; charset=utf-8 * text/plain; * text/css 3. * image/png * image/jpeg 5. * audio/mpeg * audio/ogg 6. * video/quicktime * video/mp4 7. * application/octet-stream * application/x-www-form-urlencoded; * application/json * application/xml 8. ==multipart== 1. multipart/form-data <!-- .slide: data-transition="slide-in concave-out" --> 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']` <!-- .slide: data-transition="zoom-in zoom-out" --> ---- ## 補充後端(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? <!-- .slide: data-transition="fade-in convex-out" --> ---- ![](https://i.imgur.com/adukQ63.png) ![](https://i.imgur.com/7T0kBqu.png) ```python= 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); ``` ---- ### 阿可以不用自己設拉其實 ```python= var FD = new FormData(); var xhr = new XMLHttpRequest(); xhr.open('POST', '/test', true); xhr.send(FD); ``` * ![](https://i.imgur.com/oP1OrGP.jpg) ---- ### 那..什麼時候要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] ---- #### 怎麼會現在才發現? ---- <!-- .slide: data-background="#FFFFFF" --> ##### debug msg下得不夠確實 ![](https://i.imgur.com/uGU0o7m.png) Note: 在前端先印出來看,發現前端讀到的data就已經不是預想的類型了。serverA送出的是 base64轉換過的 bytes obj,原本以為前端收到後可以直接轉傳...看來案情並不單純 ---- #### Why base64的 bytes obj變 ArrayBuffer Javascript中的 Buffers和 Views ![](https://i.imgur.com/DfArLj2.png) Note: 謝謝Chris幫忙,請教chris後了解, javascipt會將 data存放在 buffer,然後用不同的views去解讀,會讀出不同的東西。因為解讀的長度單位不同,像是 base64的 bytes obj 就要用1個 byte的單位去解讀,出來才是我們要的資料 ---- ## 將收到的 data用 Uint8解讀出來後再往下傳 `var origin_string = String.fromCharCode.apply(null, new Uint8Array(data));` ![](https://i.imgur.com/KI1H8q6.png) --- ## Oh, by the way ---- ## 好像只有莎拉不知道的小技巧0.0 ![](https://i.imgur.com/cOAqdT4.png =x200) ![](https://i.imgur.com/W8Eh1ix.png =x200) <!-- 清除個別網址的cookie `chrome://settings/siteData` --> --- # Thank U