datahandler 文件上传(基于tornado服务器实现文件上传和下载)
文件上传的服务端技术解析
常言到 ,爱有多深 、恨有多切 。tornado服务器就是这样一个矛盾体 ,它的缺点和它的优点一样,显著且强烈 。有人认为 ,文件上传是tornado的重大缺陷之一 ,它把用户上传的文件存放在内存中——这意味着多用户同时上传文件的话 ,内存的开销会急剧增加。不过我倒是觉得 ,这反倒让很多事情变得简单了 ,比如 ,你想对用户上传的内容做处理的话 ,不用再打开文件了 ,因为内容就在内存中 。再者说 ,在tornado的异步机制下,我不确定真的能够多用户同时上传文件 。这是一个有趣的问题 。
好了 ,言归正传吧 。假定文件上传的表单如下:
<formid="form_upload"action="/demo/upload"enctype="multipart/form-data"method="post"> <inputtype="file"name="want_to_upload_file_1"/><br/> <inputtype="file"name="want_to_upload_file_2"/><br/> <inputtype="submit"value="上传"/> </form>机制是允许一次上传多个文件的 。这里有几个问题需要特别说明一下 。
在提交表单之前 ,需要为form指定action和method的属性值,如果是上传文件 ,还要设置enctype=“multipart/form-data ” 。这三个属性 ,可以写在html中,也可以在submit之前用js的方法为其赋值 。
文件浏览是file类型的input标签自备的功能 ,程序员无法在浏览器框架内操作本地文件 。该标签的name属性 ,是用来区别于其他文件的标识 ,不是文件名 ,也不是文件对象 ,更不是文件内容。
上面的表单被提交到/demo/upload(假定上传的第1个文件名为dqd.jpg ,第2个文件名为intro.png) ,这个请求的对象中包含files字典 ,上传的全部文件的信息都包含在这个结构中 。我们来看看这个request.files的真实面貌:
defpost(self): printself.request.files.keys()#[uwant_to_upload_file_1,uwant_to_upload_file_2] printtype(self.request.files[want_to_upload_file_1])#list,长度为1 meta_file_1=self.request.files[want_to_upload_file_1][0] printmeta_file_1.keys()#[body,content_type,filename] printlen(meta_file_1[body])#31492 ,文件长度 printmeta_file_1[content_type]#image/jpeg printmeta_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]) withopen(file_name,wb)asfp: 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 。
fromPILimportImage importStringIO pilImg=Image.open(StringIO.StringIO(meta_file_1[body])) printpilImg.size至于如何缩放 、如何保存为文件 ,请自行检索相关资料 。
基于Ajax技术实现的文件上传客户端
假定上传文件的表单是这样的:
<formid="form_upload"action="/demo/upload"enctype="multipart/form-data"method="post"> <inputtype="file"name="wiki_img"id="wiki_img"/><br/> <inputid="doUpload"type="button"value="上传"/> </form>方法1:使用 ajaxfileupload.js
<scriptsrc="jquery.js"></script> <scriptsrc="ajaxfileupload.js"></script> <scripttype="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
<scriptsrc="jquery.js"></script> <scripttype="text/javascript"> varformData=newFormData(); 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>文件下载的服务端技术解析
相对于上传,文件的下载就简单得多 。只需要记住两点:开始前告诉浏览器要传输的文件类型 ,结束前对浏览器说拜拜 。文件类型并不是固定的 ,需要根据文件的实际情况来选择 。详情请自行检索 。
defget(self): self.set_header(Content-Type,application/octet-stream) withopen(filename,rb)asf: whileTrue: data=f.read(buf_size) ifnotdata: break self.write(data) self.finish()配合seek命令 ,可以实现更复杂的下载请求 ,比如 ,断点续传、分块下载 、ajax异步请求等 。
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!