Python中使用Flask、MongoDB搭建简易图片服务器

转自:http://www.ctolib.com/topics-43840.html

 

1、前期准备

通过 pip 或 easy_install 安装了 pymongo 之后, 就能通过 Python 调教 mongodb 了.
接着安装个 flask 用来当 web 服务器.

当然 mongo 也是得安装的. 对于 Ubuntu 用户, 特别是使用 Server 12.04 的同学, 安装最新版要略费些周折, 具体说是

1
2
3
4
5
sudo apt-key adv --keyserver <span class="hljs-string">hkp:<span class="hljs-comment">//keyserver.ubuntu.com:80 --recv 7F0CEB10
echo <span class="hljs-string">'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee <span class="hljs-regexp">/etc/apt<span class="hljs-regexp">/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb<span class="hljs-number">-10gen
</span></span></span></span></span></span>

如果你跟我一样觉得让通过上传文件名的后缀判别用户上传的什么文件完全是捏着山药当小黄瓜一样欺骗自己, 那么最好还准备个 Pillow 库

 pip install Pillow

2、正片

2.1 Flask 文件上传

Flask 官网上那个例子居然分了两截让人无从吐槽. 这里先弄个最简单的, 无论什么文件都先弄上来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<span class="hljs-keyword">import flask
app = flask.Flask(__name__)
app.debug = <span class="hljs-keyword">True
<span class="hljs-meta">@app.route('/upload', methods=['POST'])
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">upload<span class="hljs-params">():
  f = flask.request.files[<span class="hljs-string">'uploaded_file']
  <span class="hljs-keyword">print f.read()
  <span class="hljs-keyword">return flask.redirect(<span class="hljs-string">'/')
<span class="hljs-meta">@app.route('/')
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">index<span class="hljs-params">():
  <span class="hljs-keyword">return <span class="hljs-string">'''
  <!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>
  '''
<span class="hljs-keyword">if __name__ == <span class="hljs-string">'__main__':
  app.run(port=<span class="hljs-number">7777)
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

注: 在 upload 函数中, 使用 flask.request.files[KEY] 获取上传文件对象, KEY 为页面 form 中 input 的 name 值

因为是在后台输出内容, 所以测试最好拿纯文本文件来测.

2.2 保存到 mongodb

如果不那么讲究的话, 最快速基本的存储方案里只需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<span class="hljs-keyword">import pymongo
<span class="hljs-keyword">import bson.binary
<span class="hljs-keyword">from cStringIO <span class="hljs-keyword">import StringIO
app = flask.Flask(__name__)
app.debug = <span class="hljs-keyword">True
db = pymongo.MongoClient(<span class="hljs-string">'localhost', <span class="hljs-number">27017).test
<span class="hljs-function"><span class="hljs-keyword"><span style="color: #ff0000">def </span><span class="hljs-title"><span style="color: #ff0000">save_file</span><span class="hljs-params"><span style="color: #ff0000">(f):
  content = StringIO(f.read())
  db.files.save(dict(
    content= bson.binary.Binary(content.getvalue()),
  ))</span>
<span class="hljs-meta">@app.route('/upload', methods=['POST'])
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">upload<span class="hljs-params">():
  f = flask.request.files[<span class="hljs-string">'uploaded_file']
  save_file(f)
  <span class="hljs-keyword">return flask.redirect(<span class="hljs-string">'/')
 
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

把内容塞进一个  bson.binary.Binary  对象, 再把它扔进 mongodb 就可以了.

现在试试再上传个什么文件, 在 mongo shell 中通过  db.files.find() 就能看到了.

不过 content  这个域几乎肉眼无法分辨出什么东西, 即使是纯文本文件, mongo 也会显示为 Base64 编码.

2.3 提供文件访问

给定存进数据库的文件的 ID (作为 URI 的一部分), 返回给浏览器其文件内容, 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">save_file<span class="hljs-params">(f):
   content = StringIO(f.read())
   c = dict(content=bson.binary.Binary(content.getvalue()))
   db.files.save(c)
   <span class="hljs-keyword">return c[<span class="hljs-string">'_id']
<span class="hljs-meta">@app.route('/f/<fid>')
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">serve_file<span class="hljs-params">(fid):
  f = db.files.find_one(bson.objectid.ObjectId(fid))
  <span class="hljs-keyword">return f[<span class="hljs-string">'content']
<span class="hljs-meta">@app.route('/upload', methods=['POST'])
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">upload<span class="hljs-params">():
  f = flask.request.files[<span class="hljs-string">'uploaded_file']
  fid = save_file(f)
  <span class="hljs-keyword">return flask.redirect( <span class="hljs-string">'/f/' + str(fid))
 
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

上传文件之后,  upload  函数会跳转到对应的文件浏览页. 这样一来, 文本文件内容就可以正常预览了, 如果不是那么挑剔换行符跟连续空格都被浏览器吃掉的话.

2.4 当找不到文件时

有两种情况, 其一, 数据库 ID 格式就不对, 这时 pymongo 会抛异常  bson.errors.InvalidId ; 其二, 找不到对象 (!), 这时 pymongo 会返回  None .
简单起见就这样处理了

1
2
3
4
5
6
7
8
9
10
11
12
<span class="hljs-meta">@app.route('/f/<fid>')
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">serve_file<span class="hljs-params">(fid):
  <span class="hljs-keyword">import bson.errors
  <span class="hljs-keyword">try:
    f = db.files.find_one(bson.objectid.ObjectId(fid))
    <span class="hljs-keyword">if f <span class="hljs-keyword">is <span class="hljs-keyword">None:
      <span class="hljs-keyword">raise bson.errors.InvalidId()
    <span class="hljs-keyword">return f[<span class="hljs-string">'content']
  <span class="hljs-keyword">except bson.errors.InvalidId:
    flask.abort(<span class="hljs-number">404)
 
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

2.5 正确的 MIME

从现在开始要对上传的文件严格把关了, 文本文件, 狗与剪刀等皆不能上传.
判断图片文件之前说了我们动真格用 Pillow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<span class="hljs-keyword">from PIL <span class="hljs-keyword">import Image
allow_formats = set([<span class="hljs-string">'jpeg', <span class="hljs-string">'png', <span class="hljs-string">'gif'])
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">save_file<span class="hljs-params">(f):
  content = StringIO(f.read())
  <span class="hljs-keyword">try:
    mime = Image.open(content).format.lower()
    <span class="hljs-keyword">if mime <span class="hljs-keyword">not <span class="hljs-keyword">in allow_formats:
      <span class="hljs-keyword">raise IOError()
  <span class="hljs-keyword">except IOError:
    flask.abort(<span class="hljs-number">400)
  c = dict(content=bson.binary.Binary(content.getvalue()))
  db.files.save(c)
  <span class="hljs-keyword">return c[<span class="hljs-string">'_id']
 
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

然后试试上传文本文件肯定虚, 传图片文件才能正常进行. 不对, 也不正常, 因为传完跳转之后, 服务器并没有给出正确的 mimetype, 所以仍然以预览文本的方式预览了一坨二进制乱码.
要解决这个问题, 得把 MIME 一并存到数据库里面去; 并且, 在给出文件时也正确地传输 mimetype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">save_file<span class="hljs-params">(f):
  content = StringIO(f.read())
  <span class="hljs-keyword">try:
    mime = Image.open(content).format.lower()
    <span class="hljs-keyword">if mime <span class="hljs-keyword">not <span class="hljs-keyword">in allow_formats:
      <span class="hljs-keyword">raise IOError()
  <span class="hljs-keyword">except IOError:
    flask.abort(<span class="hljs-number">400)
  c = dict(content=bson.binary.Binary(content.getvalue()), mime=mime)
  db.files.save(c)
  <span class="hljs-keyword">return c[<span class="hljs-string">'_id']
<span class="hljs-meta">@app.route('/f/<fid>')
<span class="hljs-function"><span class="hljs-keyword">def <span class="hljs-title">serve_file<span class="hljs-params">(fid):
  <span class="hljs-keyword">try:
    f = db.files.find_one(bson.objectid.ObjectId(fid))
    <span class="hljs-keyword">if f <span class="hljs-keyword">is <span class="hljs-keyword">None:
      <span class="hljs-keyword">raise bson.errors.InvalidId()
    <span class="hljs-keyword">return flask.Response(f[<span class="hljs-string">'content'], mimetype=<span class="hljs-string">'image/' + f[<span class="hljs-string">'mime'])
  <span class="hljs-keyword">except bson.errors.InvalidId:
    flask.abort(<span class="hljs-number">404)
 
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>

当然这样的话原来存进去的东西可没有 mime 这个属性, 所以最好先去 mongo shell 用  db.files.drop()  清掉原来的数据.














本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/bonelee/p/6513455.html,如需转载请自行联系原作者

上一篇:TcpDump 抓包 传输数据包


下一篇:mysql 行号 获取指定行数据