亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Python中使用Flask、MongoDB搭建簡(jiǎn)易圖片服務(wù)器

 更新時(shí)間:2015年02月04日 09:55:43   投稿:junjie  
這篇文章主要介紹了Python中使用Flask、MongoDB搭建簡(jiǎn)易圖片服務(wù)器,本文是一個(gè)詳細(xì)完整的教程,需要的朋友可以參考下

1、前期準(zhǔn)備

通過 pip 或 easy_install 安裝了 pymongo 之后, 就能通過 Python 調(diào)教 mongodb 了.
接著安裝個(gè) flask 用來當(dāng) web 服務(wù)器.

當(dāng)然 mongo 也是得安裝的. 對(duì)于 Ubuntu 用戶, 特別是使用 Server 12.04 的同學(xué), 安裝最新版要略費(fèi)些周折, 具體說是

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

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

復(fù)制代碼 代碼如下:

pip install Pillow

或 (更適合 Windows 用戶)

復(fù)制代碼 代碼如下:

easy_install Pillow

2、正片

2.1 Flask 文件上傳

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

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 '''
  <!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 函數(shù)中, 使用 flask.request.files[KEY] 獲取上傳文件對(duì)象, KEY 為頁(yè)面 form 中 input 的 name 值

因?yàn)槭窃诤笈_(tái)輸出內(nèi)容, 所以測(cè)試最好拿純文本文件來測(cè).

2.2 保存到 mongodb

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

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('/')

把內(nèi)容塞進(jìn)一個(gè)  bson.binary.Binary  對(duì)象, 再把它扔進(jìn) mongodb 就可以了.

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

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

2.3 提供文件訪問

給定存進(jìn)數(shù)據(jù)庫(kù)的文件的 ID (作為 URI 的一部分), 返回給瀏覽器其文件內(nèi)容, 如下

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  函數(shù)會(huì)跳轉(zhuǎn)到對(duì)應(yīng)的文件瀏覽頁(yè). 這樣一來, 文本文件內(nèi)容就可以正常預(yù)覽了, 如果不是那么挑剔換行符跟連續(xù)空格都被瀏覽器吃掉的話.

2.4 當(dāng)找不到文件時(shí)

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

@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

從現(xiàn)在開始要對(duì)上傳的文件嚴(yán)格把關(guān)了, 文本文件, 狗與剪刀等皆不能上傳.
判斷圖片文件之前說了我們動(dòng)真格用 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']

然后試試上傳文本文件肯定虛, 傳圖片文件才能正常進(jìn)行. 不對(duì), 也不正常, 因?yàn)閭魍晏D(zhuǎn)之后, 服務(wù)器并沒有給出正確的 mimetype, 所以仍然以預(yù)覽文本的方式預(yù)覽了一坨二進(jìn)制亂碼.
要解決這個(gè)問題, 得把 MIME 一并存到數(shù)據(jù)庫(kù)里面去; 并且, 在給出文件時(shí)也正確地傳輸 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)

當(dāng)然這樣的話原來存進(jìn)去的東西可沒有 mime 這個(gè)屬性, 所以最好先去 mongo shell 用  db.files.drop()  清掉原來的數(shù)據(jù).

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

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

體現(xiàn)為代碼是

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/<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)

然后, 得弄個(gè)腳本把數(shù)據(jù)庫(kù)里面已經(jīng)有的圖片給加上時(shí)間戳.
順帶吐個(gè)槽, 其實(shí) NoSQL DB 在這種環(huán)境下根本體現(xiàn)不出任何優(yōu)勢(shì), 用起來跟 RDB 幾乎沒兩樣.

2.7 利用 SHA-1 排重

與冰箱里的可樂不同, 大部分情況下你肯定不希望數(shù)據(jù)庫(kù)里面出現(xiàn)一大波完全一樣的圖片. 圖片, 連同其 EXIFF 之類的數(shù)據(jù)信息, 在數(shù)據(jù)庫(kù)中應(yīng)該是惟一的, 這時(shí)使用略強(qiáng)一點(diǎn)的散列技術(shù)來檢測(cè)是再合適不過了.

達(dá)到這個(gè)目的最簡(jiǎn)單的就是建立一個(gè)  SHA-1  惟一索引, 這樣數(shù)據(jù)庫(kù)就會(huì)阻止相同的東西被放進(jìn)去.

在 MongoDB 中表中建立惟一 索引 , 執(zhí)行 (Mongo 控制臺(tái)中)

復(fù)制代碼 代碼如下:

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

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

解決方案有三個(gè):

1)刪掉現(xiàn)在所有的數(shù)據(jù) (一定是測(cè)試數(shù)據(jù)庫(kù)才用這種不負(fù)責(zé)任的方式吧!)
2)建立一個(gè) sparse 索引, 這個(gè)索引不要求幽靈屬性惟一, 不過出現(xiàn)多個(gè) null 值還是會(huì)判定重復(fù) (不管現(xiàn)有數(shù)據(jù)的話可以這么搞)
3)寫個(gè)腳本跑一次數(shù)據(jù)庫(kù), 把所有已經(jīng)存入的數(shù)據(jù)翻出來, 重新計(jì)算 SHA-1, 再存進(jìn)去
具體做法隨意. 假定現(xiàn)在這個(gè)問題已經(jīng)搞定了, 索引也弄好了, 那么剩是 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']

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

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/<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

相關(guān)文章

最新評(píng)論