---
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