聊聊阿里雲 OSS 的轉義設計問題
今天同事向我反饋,當我們的靜態資源有 中文名混雜 + 和空格 時,得到的連結並不能開啟,返回的是 404.
舉個例子,假定我們的資源為:
http://example-resource.oss-cn-shanghai.aliyuncs.com/23232323/中文 + 檔案-file.ec296f20-d67b-11e8-a623-7b28809dc3c9.js
那麼在瀏覽器中訪問的檔名應該被轉義為:
%E4%B8%AD%E6%96%87%20+%20%E6%96%87%E4%BB%B6-file.ec296f20-d67b-11e8-a623-7b28809dc3c9.js
注意,在這裡,空格被轉義為 %20
,而 +
沒有進行任何處理。
同時,JavaScript 中的 encodeURI
和 new URL()
都是符合這一規則的。
但是在阿里雲 OSS 中,你可以在 404 頁面看到他們後端請求的檔案:
此時的一個問題:到底是存錯了還是取錯了——其實在這裡我們基本可以看出取的是有問題的,因為對於這個 path 的語義應該是 +
,卻被錯誤的處理成了空格,不過考慮到嚴謹性,還是確認了一下儲存的 Object,確定是取的問題。
接下來的問題是:
- SDK 給我們返回的阿里雲連結是可以直接訪問的,而我們自定義拼接的連結是不可訪問的,他們做了什麼特殊處理
- 到底是哪裡出了問題
因此,不得已的只能把 Node SDK 原始碼看了一遍……終於發現了他們的騷操作:
ofollow,noindex" target="_blank">https://github.com/ali-sdk/ali-oss/blob/master/lib/client.js#L366
在 encode 之後人工 2B,誰都比不過——
第二個問題,到底是誰的錯?
於是正好上 so 搜了一下,發現了一波標準: https://stackoverflow.com/questions/2678551/when-to-encode-space-to-plus-or-20
+
means a space only in application/x-www-form-urlencoded
content, such as the query part of a URL
Now, the HTML 2.0 Specification (RFC1866) explicitly said, in section 8.2.2, that the Query part of a GET request's URL string should be encoded as ·application/x-www-form-urlencoded·. This, in theory, suggests that it's legal to use a '+' in the URL in the query string (after the '?').
也就是說,只有 application/x-www-form-urlencoded
和 querystring
中的空格 = +
, +
= %2B
,其他情況下 +
不應該被特殊處理,很顯然阿里雲 OSS 的實現是錯誤的。
得到問題的答案之後,基本定位到是 OSS 後端的轉義問題,但是去懟 OSS 的時候,他們用「這是 Feature」、「為了複雜規則更好的相容」、「設計就是這樣」、「使用者最好自己 encode」來答覆,這裡有涉及到內容儲存的另一個結構了:
對於內容儲存而言,會將路徑也作為檔名的一部分,即上例中的檔名是:
23232323/中文 + 檔案-file.ec296f20-d67b-11e8-a623-7b28809dc3c9.js
那麼我們處理的時候,如果使用 encodeURI
,則不會轉義 +
,如果使用 encodeURIComponent
,則會把 /
一起轉義,如果在儲存前轉義,就無法保留正確的檔名——非常矛盾,這大概也就是為什麼 SDK 用了 + => 2B
的黑魔法吧。
不過我也有個大膽的猜測,因為阿里雲這段程式碼非常年長而穩定了,而我們這樣的考據使用者其實並不多,湊合用黑魔法也能用,所以所謂的「Feature」,就是——改了萬一掛了我可背不起鍋,即使是加一個相容 Feature,也是有可能掛的,所以最好的方法還是讓使用者繼續用黑魔法,但是建議大家以後在做類似的實現的時候不要理所當然的以為 url 中的 +
必然等於空格,在標準中是有明確規定的。
小插曲:最開始以為是 JavaScript encode 庫有問題,怎麼都不能和阿里 encode 的結果一致,還罵了一聲 JavaScript 辣雞,如果是 PHP 估計就沒問題(喂。定位了一天並且和阿里雲撕逼之後只能說——阿里雲辣雞(喂。