一、問題的由來
最近有個項目,叫做文檔服務資源中心,類似於七牛,為各個業務系統提供統一的文件資源服務,包括文件的存儲、操作管理、下載、預覽等。在做文件存儲的時候,遇到了這個當指定上傳的文件名為中文時,Flask框架的服務端無法解析成文件,而是當成一般的表單數據處理。我們在文件存儲的實現架構如下圖:
客戶端業務系統(Python開發的)通過調用python-sdk中的上文文件API上傳文件。按照requests這個類庫上傳文件的格式要求,必須指明文件的文件名。所以,在API開發完成之后,當上傳的文件的文件名是中文的時候,測試沒通過。
二、代碼解析
客戶端測試代碼:
請注意,在files變量中,file對應的元組值的第一個參數“十三五”發展規划.docx”就是文件名,是中文格式。
服務端代碼(簡化后):
注意,在try..exception中的代碼,判斷是否獲取文件成功。
運行:
先運行服務端代碼,然后運行測試代碼。結果如下:
進入調試模式,查看request變量的值,重點關注files跟form屬性。如下圖:
從上圖可以,files的屬性為空,而把file當成了form數據的屬性,屬性的值為文件的二進制內容數據。
三、問題緣由的查找
(1.)下載fiddler抓包工具。發現requests向flask網站服務傳遞如下數據。
特別注意,紅框中的filename*這一段。
(2.)讀Flask的源代碼,特別注意Flask對上傳文件的解析與處理。發現位於werkzeug下的formparser.py里的parse_lines方法中判斷語句(位於文件的413行)
可以得出結論,Flask是根據名稱為filename的鍵來判斷Requests傳遞過來的數據是否是文件內容。而在上面通過fiddler抓包工具可知,Requests傳遞了filename*這個鍵的名稱,多了一個*號。所以,Flask認為不是傳遞的文件,從而當成了一般屬性處理。
(3.)那Requests為什么會傳遞filename*這樣的鍵呢。再次跟蹤並閱讀Requests的源代碼。返現Requests會對filename做編碼的特殊處理。代碼位於requestsàpackagesàurllib3—>fields.py(第22行的format_header_param方法)。
對不是ascii編碼的內容,進行了rfc 2231編碼,並組織成key*= rfc 2231這種格式。所以就有了上述的filename*這種格式的鍵值對。
四、解決辦法
有三種解決辦法。
(1.)修改Requests的源代碼
requestsàpackagesàurllib3—>fields.py—>format_header_param方法的以下代碼
改成
(2.)修改Flask的源代碼
werkzeug下的formparser.py里的parse_lines方法中的413行開始的以下代碼
改成
並導入相應的包。
(3.)修改調用Request的post方法時files變量的filename賦值,將其改成英文,比如設置成固定的file_name,而將真正的filename(最好帶有后綴)當成data參數中的鍵名為file_name(根據項目情況,自由定義)的值傳遞給服務端,服務端去讀取file_name對應的值就行。實現代碼如下:
總結:修改Flask、requests類庫的源代碼不太理想,不便於部署,而且可能會發生其他意想不到的問題。建議采用第3中折中解決辦法。