輕巧 Python Web Framework - Bottle 練習
3 | 4,449 |
愈來愈多好用的開源專案是用 Python 開發的,對我來說,開源專案最迷人之處莫過於 - 踩到 Bug 自己抓,功能不夠自己加,這麼說來,Python 加減也該會看會改。
最近從某個開源專案認識新東西 - Bottle,一個輕量級的 Python 網站框架,不依賴其他套件,只用 Python 標準程式庫,一支 .py 檔即可運作,跟當初看到 NancyFx 一樣,我對這種小而美的輕巧兵器毫無抵抗力! 便把玩了一下,準備為該專案新增檔案下載功能。
為方便與 Docker 接軌,我在 Linux (CentOS) 上練習。準備開發環境花了點功夫,筆記以下:
- CentOS 7 內建的 Python 是 2.7.5,要先升級成 3.x。CentOS 7.7+ 版本可直接安裝:
cat /etc/redhat-release #檢查 CentOS 版本,確認是 7.7+ sudo yum update -y sudo yum install -y python3
- CentOS 的 git 也要額外安裝,
sudo yum install git
裝的是 1.8.3 版,版本過舊,參考在 CentOS 6/7 以 yum 安裝 git 2從 IUS 來源安裝 git 2.2.4:sudo yum install https://repo.ius.io/ius-release-el7.rpm sudo yum provides git* # 找 IUS Git 套件的名稱,查得 git222 sudo yum install git222
接著我練習用 Bottle 建一個簡單檔案下載網站。開始前要先安裝 Bottle 模組(實際上,它只有一個檔案,bottle.py,直接下載來用也成):
sudo pip3 install bottle
假想情境是結果檔案會被放在特定目錄下,要寫個簡易下載介面讓使用者可透過瀏覽器下載,因為是家用私人服務,就不花時間寫權限控管,開放匿名存取。我設計了三個 Action,/files 顯示特定目錄下的結果檔案清單,/download/hashId 可下載檔案,/delete/hashId 則是刪除檔案。Bottle 透過 @route("/action") 定義不同網址的回應行為,@route("/action/<varName>") 可從路由取參數。讀檔及刪檔時不使用檔名,而是採取雜湊碼比對,存取範圍嚴格限制在指定目錄內,可降低資安風險。取得檔案清單後轉成 Dictionary,以檔名雜湊碼為 Key,使用白名單方式限定只能下載項目,避免有人在路徑參數動手腳幹壞事。對映到檔案後,再使用 Bottle 提供的 static_file 傳回檔案內容。短短幾行,Python 門外漢搞了一陣子才寫好,大部分的時間花在查 C# Directory.GetFiles()、ToDictionary()、string.Join()、File.Delete() 對映的 Python 寫法。完整程式如下:
from bottle import route, run, template, static_file, abort, redirect
from os import listdir,remove
from os.path import isfile, isdir, join, dirname
filesPath = join(dirname(__file__), 'files')
def toLink(n, f):
return template('<li><a href="/download/{{n}}" download="{{n}}">{{text}}</a> <a href="/delete/{{n}}" class="del">×</a></li>', n=n, text=f)
def getFiles():
filesDict = dict()
for f in listdir(filesPath):
filesDict[str(hash(f))] = f
return filesDict
@route('/')
def index():
return redirect('/files')
@route('/files')
def files():
html = ['<style>.del {color:red;text-decoration:none}</style><ul>']
files = getFiles()
for key in files.keys():
html.append(toLink(key, files[key]))
html.append('</ul>')
return '\n'.join(html)
@route('/download/<n>')
def download(n):
files = getFiles()
if n not in files:
abort(404, 'file not found - ' + n)
return static_file(files[n], root=filesPath)
@route('/delete/<n>')
def delete(n):
files = getFiles()
if n not in files:
abort(404, 'no such file')
remove(join(filesPath, files[n]))
return redirect('/files')
run(host='localhost', port=8080)
比預期順利,程式在 Linux 跑了起來,成功完成讀檔、刪檔操作:
就醬,我寫出人生第一個實用 Python 小網站,默默前進了一步。
但我必須說,武功練得愈深,換兵器就愈痛苦。一來是原本用 C# 彈指可成的功能,換用 Python 寫頻頻卡關,難免心浮氣躁;二則常會想到簡潔性、模組化、效能、安全上的進階議題,初學者沒想那麼多傻傻寫好會動就當 OK (無知有無知的快樂呀),老鳥既然想到就不能無視,但用陌生語言實現這類進階要求格外吃力。就像平常說中文字句斟酌出口成章慣了,改用英文便會卡在某個成語或形容詞不會翻,想用對倒裝補述分詞構句又對文法沒把握,吞吞吐吐擠不出半句。而最要命的是,邊寫心中不斷浮現大哉問 - 人生苦短,明明用 C# 可輕鬆完成,拎杯真的要花時間再學一套功用重複的程式語言嗎?我應該只會將 Python 練到看得懂可以維護修改別人的專案就好,需求複雜到一定程度還是寫 .NET 較有效率,反正跨平台已不是 .NET 的限制,退一百步,還有呼叫外部程式或 WebAPI 方式整合 Python 的大絕可用,呵。
補充:如果對 Python 有興趣想入門的同學,可以試試微軟為初學者製作的 Python 教學影片。
Trying to use Python Bottle to write a mini file downloading service in CentOS.
Comments
# by Kevan
晚輩參見真人,在下有禮了
# by Ho.Chun
最後的總結太棒了!!
# by jamesli
看起來跟Flask很像