文件上传的服务端技术解析
俗话说,爱有多深,恨就有多深。龙卷风服务器就是这样一个悖论,它的缺点与它的优点一样显着和强大。有人认为文件上传是tornado的一大缺陷,它将用户上传的文件存储在内存中——这意味着如果多个用户同时上传文件,内存开销会急剧增加。但我认为这让很多事情变得更容易。例如,如果要处理用户上传的内容,则不需要再打开文件,因为内容在内存中。另外,在tornado的异步机制下,我不确定多个用户是否可以同时上传文件。这是个有趣的问题。
好了,言归正传吧。假定文件上传的表单如下:
<form id="form_upload" action="/demo/upload" enctype="multipart/form-data" method="post">
<input type="file" name="want_to_upload_file_1"/><br/>
<input type="file" name="want_to_upload_file_2"/><br/>
<input type="submit" value="上传"/>
</form>
该机制是允许一次上传多个文件。有几个问题需要具体解决。
在提交表单之前,需要为表单指定action和method的属性值。如果要上传文件,还必须设置 enctype="multipart/form-data"。这三个属性可以用html写,也可以在提交前用js赋值。
文件浏览是文件类型的输入标签提供的功能,程序员无法在浏览器框架内操作本地文件。标签的name属性是用来和其他文件区分开来的,不是文件名,也不是文件对象,也不是文件内容。
上述表单提交到/demo/upload(假设上传的第一个文件名为dqd.jpg,第二个文件名为intro.png),该请求的对象包含文件字典,所有上传文件的信息为包含在这个结构中。让我们看看这个 request.files 到底长什么样:
def post(self):
print self.request.files.keys() # [u'want_to_upload_file_1', u'want_to_upload_file_2']
print type(self.request.files['want_to_upload_file_1']) # list,长度为1
meta_file_1 = self.request.files['want_to_upload_file_1'][0]
print meta_file_1.keys() # ['body', 'content_type', 'filename']
print len(meta_file_1['body']) # 31492,文件长度
print meta_file_1['content_type'] # image/jpeg
print meta_file_1['filename'] # dqd.jpg
有了这些材料,我们就可以无所不能地响应客户的需求。例如,不做任何处理,只需将原始文件名保存在指定路径中(假设保存在/static/image/wiki目录下):
PROJECT_PATH = os.path.split(os.path.realpath(__file__))[0]
upload_path = os.path.join(PROJECT_PATH, 'static', 'image', 'wiki')
file_name = os.path.join(upload_path, meta_file_1['filename'])
with open(file_name, 'wb') as fp:
fp.write(meta_file_1['body'])
很多情况下,需要对用户上传的文件进行重命名(例如使用时间戳作为文件名),但文件后缀名保持不变。
fn, ext = os.path.splitext(meta_file_1['filename'])
fn = '%d%s' % (time.time()*1000, ext)
file_name = os.path.join(upload_path, fn)
如果需要查看用户上传的文件类型,请使用文件的content_type而不是文件扩展名,因为前者更规范。例如,对于JPEG类型的图像文件,其后缀名可以是.jpg|.jpeg|.JPG|.JPEG之一,前者只有“image/jpeg”的表示。
关于文件的content_type,网上资料多如牛毛,请自行搜索。
在处理用户上传的图片文件时,除了限制文件大小外,有时还需要进行缩放,甚至会生成缩略图。这时候就需要将文件内容转换成便于处理的图片对象,比如pil的Image。
from PIL import Image
import StringIO
pilImg = Image.open(StringIO.StringIO(meta_file_1['body']))
print pilImg.size
至于如何缩放,如何保存为文件,请自行搜索相关资料。
基于Ajax技术实现的文件上传客户端
假定上传文件的表单是这样的:
<form id="form_upload" action="/demo/upload" enctype="multipart/form-data" method="post">
<input type="file" name="wiki_img" id="wiki_img" /><br />
<input id="doUpload" type="button" value="上传" />
</form>
方法1:使用 ajaxfileupload.js
<script src="jquery.js"></script>
<script src="ajaxfileupload.js"></script>
<script type="text/javascript">
$("#doUpload").click(function(){
$.ajaxFileUpload({
url:'/demo/upload',
secureuri:false,
fileElementId:'wiki_img',
dataType: 'json',
success: function(data){
alert(data);
}
});
});
</script>
方法2:仅依赖 jquery.js
<script src="jquery.js"></script>
<script type="text/javascript">
var formData = new FormData();
formData.append("file", $("#wiki_img")[0].files[0]);
formData.append("filename", $("#wiki_img").val());
$.ajax({
url : '/demo/upload',
type : 'POST',
async : false,
data : formData,
processData : false,
contentType : false,
beforeSend:function(){
$("#upload_tips").html("正在进行,请稍候");
},
success : function(data) {
alert(data);
}
});
</script>
文件下载的服务端技术解析
与上传相比,下载文件要简单得多。只需记住两件事:在开始之前告诉你的浏览器要传输哪些文件类型,并在结束之前与浏览器说再见。文件类型不固定,需要根据文件的实际情况进行选择。请搜索详情。
def get(self):
self.set_header ('Content-Type', 'application/octet-stream')
with open(filename, 'rb') as f:
while True:
data = f.read(buf_size)
if not data:
break
self.write(data)
self.finish()
使用 seek 命令可以实现更复杂的下载请求,比如断点续传、分块下载、异步 ajax 请求等。
众多
,尽在python学习网,欢迎在线学习!
本文为原创文章,版权归知行编程网所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ xlutils如何在python中写入数据?12/28
- ♥ python新手适合学习哪些知识12/18
- ♥ python十六进制数代表什么09/17
- ♥ python中“\n”的转义是什么08/22
- ♥ python安装第三方库失败怎么办09/25
- ♥ python中的sigmoid函数是什么09/13
内容反馈