国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

Python中使用Flask、MongoDB搭建簡易圖片服務器

2019-11-25 18:00:23
字體:
來源:轉載
供稿:網友

1、前期準備

通過 pip 或 easy_install 安裝了 pymongo 之后, 就能通過 Python 調教 mongodb 了.
接著安裝個 flask 用來當 web 服務器.

當然 mongo 也是得安裝的. 對于 Ubuntu 用戶, 特別是使用 Server 12.04 的同學, 安裝最新版要略費些周折, 具體說是

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.listsudo apt-get updatesudo apt-get install mongodb-10gen

如果你跟我一樣覺得讓通過上傳文件名的后綴判別用戶上傳的什么文件完全是捏著山藥當小黃瓜一樣欺騙自己, 那么最好還準備個 Pillow 庫

復制代碼 代碼如下:

pip install Pillow

或 (更適合 Windows 用戶)

復制代碼 代碼如下:

easy_install Pillow

2、正片

2.1 Flask 文件上傳

Flask 官網上那個例子居然分了兩截讓人無從吐槽. 這里先弄個最簡單的, 無論什么文件都先弄上來

import flaskapp = flask.Flask(__name__)app.debug = True@app.route('/upload', methods=['POST'])def upload():  f = flask.request.files['uploaded_file']  print f.read()  return flask.redirect('/')@app.route('/')def index():  return '''  <!doctype html>  <html>  <body>  <form action='/upload' method='post' enctype='multipart/form-data'>     <input type='file' name='uploaded_file'>     <input type='submit' value='Upload'>  </form>  '''if __name__ == '__main__':  app.run(port=7777)

注: 在 upload 函數中, 使用 flask.request.files[KEY] 獲取上傳文件對象, KEY 為頁面 form 中 input 的 name 值

因為是在后臺輸出內容, 所以測試最好拿純文本文件來測.

2.2 保存到 mongodb

如果不那么講究的話, 最快速基本的存儲方案里只需要

import pymongoimport bson.binaryfrom cStringIO import StringIOapp = flask.Flask(__name__)app.debug = Truedb = pymongo.MongoClient('localhost', 27017).testdef save_file(f):  content = StringIO(f.read())  db.files.save(dict(    content= bson.binary.Binary(content.getvalue()),  ))@app.route('/upload', methods=['POST'])def upload():  f = flask.request.files['uploaded_file']  save_file(f)  return flask.redirect('/')

把內容塞進一個  bson.binary.Binary  對象, 再把它扔進 mongodb 就可以了.

現在試試再上傳個什么文件, 在 mongo shell 中通過  db.files.find() 就能看到了.

不過 content  這個域幾乎肉眼無法分辨出什么東西, 即使是純文本文件, mongo 也會顯示為 Base64 編碼.

2.3 提供文件訪問

給定存進數據庫的文件的 ID (作為 URI 的一部分), 返回給瀏覽器其文件內容, 如下

def save_file(f):   content = StringIO(f.read())   c = dict(content=bson.binary.Binary(content.getvalue()))   db.files.save(c)   return c['_id']@app.route('/f/<fid>')def serve_file(fid):  f = db.files.find_one(bson.objectid.ObjectId(fid))  return f['content']@app.route('/upload', methods=['POST'])def upload():  f = flask.request.files['uploaded_file']  fid = save_file(f)  return flask.redirect( '/f/' + str(fid))

上傳文件之后,  upload  函數會跳轉到對應的文件瀏覽頁. 這樣一來, 文本文件內容就可以正常預覽了, 如果不是那么挑剔換行符跟連續空格都被瀏覽器吃掉的話.

2.4 當找不到文件時

有兩種情況, 其一, 數據庫 ID 格式就不對, 這時 pymongo 會拋異常  bson.errors.InvalidId ; 其二, 找不到對象 (!), 這時 pymongo 會返回  None .
簡單起見就這樣處理了

@app.route('/f/<fid>')def serve_file(fid):  import bson.errors  try:    f = db.files.find_one(bson.objectid.ObjectId(fid))    if f is None:      raise bson.errors.InvalidId()    return f['content']  except bson.errors.InvalidId:    flask.abort(404)

2.5 正確的 MIME

從現在開始要對上傳的文件嚴格把關了, 文本文件, 狗與剪刀等皆不能上傳.
判斷圖片文件之前說了我們動真格用 Pillow

from PIL import Imageallow_formats = set(['jpeg', 'png', 'gif'])def save_file(f):  content = StringIO(f.read())  try:    mime = Image.open(content).format.lower()    if mime not in allow_formats:      raise IOError()  except IOError:    flask.abort(400)  c = dict(content=bson.binary.Binary(content.getvalue()))  db.files.save(c)  return c['_id']

然后試試上傳文本文件肯定虛, 傳圖片文件才能正常進行. 不對, 也不正常, 因為傳完跳轉之后, 服務器并沒有給出正確的 mimetype, 所以仍然以預覽文本的方式預覽了一坨二進制亂碼.
要解決這個問題, 得把 MIME 一并存到數據庫里面去; 并且, 在給出文件時也正確地傳輸 mimetype

def save_file(f):  content = StringIO(f.read())  try:    mime = Image.open(content).format.lower()    if mime not in allow_formats:      raise IOError()  except IOError:    flask.abort(400)  c = dict(content=bson.binary.Binary(content.getvalue()), mime=mime)  db.files.save(c)  return c['_id']@app.route('/f/<fid>')def serve_file(fid):  try:    f = db.files.find_one(bson.objectid.ObjectId(fid))    if f is None:      raise bson.errors.InvalidId()    return flask.Response(f['content'], mimetype='image/' + f['mime'])  except bson.errors.InvalidId:    flask.abort(404)

當然這樣的話原來存進去的東西可沒有 mime 這個屬性, 所以最好先去 mongo shell 用  db.files.drop()  清掉原來的數據.

2.6 根據上傳時間給出 NOT MODIFIED
利用 HTTP 304 NOT MODIFIED 可以盡可能壓榨與利用瀏覽器緩存和節省帶寬. 這需要三個操作

1)、記錄文件最后上傳的時間
2)、當瀏覽器請求這個文件時, 向請求頭里塞一個時間戳字符串
3)、當瀏覽器請求文件時, 從請求頭中嘗試獲取這個時間戳, 如果與文件的時間戳一致, 就直接 304

體現為代碼是

import datetimedef save_file(f):  content = StringIO(f.read())  try:    mime = Image.open(content).format.lower()    if mime not in allow_formats:      raise IOError()  except IOError:    flask.abort(400)  c = dict(    content=bson.binary.Binary(content.getvalue()),    mime=mime,     time=datetime.datetime.utcnow(),  )  db.files.save(c)  return c['_id']@app.route('/f/<fid>')def serve_file(fid):  try:    f = db.files.find_one(bson.objectid.ObjectId(fid))    if f is None:      raise bson.errors.InvalidId()    if flask.request.headers.get('If-Modified-Since') == f['time'].ctime():      return flask.Response(status=304)    resp = flask.Response(f['content'], mimetype='image/' + f['mime'])    resp.headers['Last-Modified'] = f['time'].ctime()    return resp  except bson.errors.InvalidId:    flask.abort(404)

然后, 得弄個腳本把數據庫里面已經有的圖片給加上時間戳.
順帶吐個槽, 其實 NoSQL DB 在這種環境下根本體現不出任何優勢, 用起來跟 RDB 幾乎沒兩樣.

2.7 利用 SHA-1 排重

與冰箱里的可樂不同, 大部分情況下你肯定不希望數據庫里面出現一大波完全一樣的圖片. 圖片, 連同其 EXIFF 之類的數據信息, 在數據庫中應該是惟一的, 這時使用略強一點的散列技術來檢測是再合適不過了.

達到這個目的最簡單的就是建立一個  SHA-1  惟一索引, 這樣數據庫就會阻止相同的東西被放進去.

在 MongoDB 中表中建立惟一 索引 , 執行 (Mongo 控制臺中)

復制代碼 代碼如下:

db.files.ensureIndex({sha1: 1}, {unique: true})

如果你的庫中有多條記錄的話, MongoDB 會給報個錯. 這看起來很和諧無害的索引操作被告知數據庫中有重復的取值 null (實際上目前數據庫里已有的條目根本沒有這個屬性). 與一般的 RDB 不同的是, MongoDB 規定 null, 或不存在的屬性值也是一種相同的屬性值, 所以這些幽靈屬性會導致惟一索引無法建立.

解決方案有三個:

1)刪掉現在所有的數據 (一定是測試數據庫才用這種不負責任的方式吧!)
2)建立一個 sparse 索引, 這個索引不要求幽靈屬性惟一, 不過出現多個 null 值還是會判定重復 (不管現有數據的話可以這么搞)
3)寫個腳本跑一次數據庫, 把所有已經存入的數據翻出來, 重新計算 SHA-1, 再存進去
具體做法隨意. 假定現在這個問題已經搞定了, 索引也弄好了, 那么剩是 Python 代碼的事情了.

import hashlibdef save_file(f):  content = StringIO(f.read())  try:    mime = Image.open(content).format.lower()    if mime not in allow_formats:      raise IOError()  except IOError:    flask.abort(400)  sha1 = hashlib.sha1(content.getvalue()).hexdigest()  c = dict(    content=bson.binary.Binary(content.getvalue()),    mime=mime,    time=datetime.datetime.utcnow(),    sha1=sha1,  )  try:    db.files.save(c)  except pymongo.errors.DuplicateKeyError:    pass  return c['_id']

在上傳文件這一環就沒問題了. 不過, 按照上面這個邏輯, 如果上傳了一個已經存在的文件, 返回  c['_id']  將會是一個不存在的數據 ID. 修正這個問題, 最好是返回  sha1 , 另外, 在訪問文件時, 相應地修改為用文件 SHA-1 訪問, 而不是用 ID.
最后修改的結果及本篇完整源代碼如下 :

import hashlibimport datetimeimport flaskimport pymongoimport bson.binaryimport bson.objectidimport bson.errorsfrom cStringIO import StringIOfrom PIL import Imageapp = flask.Flask(__name__)app.debug = Truedb = pymongo.MongoClient('localhost', 27017).testallow_formats = set(['jpeg', 'png', 'gif'])def save_file(f):  content = StringIO(f.read())  try:    mime = Image.open(content).format.lower()    if mime not in allow_formats:      raise IOError()  except IOError:    flask.abort(400)  sha1 = hashlib.sha1(content.getvalue()).hexdigest()  c = dict(    content=bson.binary.Binary(content.getvalue()),    mime=mime,    time=datetime.datetime.utcnow(),    sha1=sha1,  )  try:    db.files.save(c)  except pymongo.errors.DuplicateKeyError:    pass  return sha1@app.route('/f/<sha1>')def serve_file(sha1):  try:    f = db.files.find_one({'sha1': sha1})    if f is None:      raise bson.errors.InvalidId()    if flask.request.headers.get('If-Modified-Since') == f['time'].ctime():      return flask.Response(status=304)    resp = flask.Response(f['content'], mimetype='image/' + f['mime'])    resp.headers['Last-Modified'] = f['time'].ctime()    return resp  except bson.errors.InvalidId:    flask.abort(404)@app.route('/upload', methods=['POST'])def upload():  f = flask.request.files['uploaded_file']  sha1 = save_file(f)  return flask.redirect('/f/' + str(sha1))@app.route('/')def index():  return '''  <!doctype html>  <html>  <body>  <form action='/upload' method='post' enctype='multipart/form-data'>     <input type='file' name='uploaded_file'>     <input type='submit' value='Upload'>  </form>  '''if __name__ == '__main__':  app.run(port=7777)


3、REF

Developing RESTful Web APIs with Python, Flask and MongoDB

http://www.slideshare.net/nicolaiarocci/developing-restful-web-apis-with-python-flask-and-mongodb

https://github.com/nicolaiarocci/eve

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 乐陵市| 宝应县| 锡林浩特市| 漠河县| 甘肃省| 噶尔县| 新闻| 云安县| 盐城市| 乐昌市| 清丰县| 偃师市| 台州市| 渝北区| 临清市| 恭城| 南开区| 金溪县| 天全县| 潮安县| 灵丘县| 京山县| 会东县| 阿坝| 宜丰县| 仁寿县| 清河县| 铁岭县| 古交市| 东港市| 静乐县| 乌海市| 眉山市| 深州市| 兴海县| 沈阳市| 建瓯市| 贡嘎县| 湘西| 广河县| 北川|