日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

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

系統 1884 0

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 7F0CEB10
echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-10gen

          

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

復制代碼 代碼如下:

pip install Pillow

或 (更適合 Windows 用戶)

復制代碼 代碼如下:

easy_install Pillow

2、正片

2.1 Flask 文件上傳

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

            
import flask
app = 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 '''
  
  
  
  
            
''' if __name__ == '__main__': app.run(port=7777)

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

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

2.2 保存到 mongodb

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

            
import pymongo
import bson.binary
from cStringIO import StringIO
app = flask.Flask(__name__)
app.debug = True
db = pymongo.MongoClient('localhost', 27017).test
def 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/
            
              ')
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/
            
              ')
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 Image
allow_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/
            
              ')
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 datetime
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,
     time=datetime.datetime.utcnow(),
  )
  db.files.save(c)
  return c['_id']
@app.route('/f/
            
              ')
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 hashlib
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 c['_id']


          

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

            
import hashlib
import datetime
import flask
import pymongo
import bson.binary
import bson.objectid
import bson.errors
from cStringIO import StringIO
from PIL import Image
app = flask.Flask(__name__)
app.debug = True
db = pymongo.MongoClient('localhost', 27017).test
allow_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/
            
              ')
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 '''
  
  
  
  
              
''' 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


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦?。?!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 碌曲县| 钦州市| 望谟县| 会同县| 西丰县| 嘉荫县| 山东省| 临江市| 伊通| 封开县| 莱芜市| 潢川县| 大洼县| 西平县| 疏附县| 封开县| 轮台县| 柳州市| 嘉义县| 郴州市| 耒阳市| 长顺县| 尼勒克县| 昌邑市| 钦州市| 漯河市| 修武县| 望奎县| 临朐县| 临汾市| 成安县| 盘山县| 芒康县| 高雄县| 商城县| 乌兰察布市| 红河县| 临澧县| 青海省| 武强县| 德庆县|