upload(文件上传及显示处理)¶
使用werkzeug进行处理¶
在Uliweb中对上传有一定的封装处理,下面先介绍一下,不使用Uliweb提供的功能,如何 使用底层的werkzeug来处理文件上传。
当用户在前端通过 <input type="file">
来上传一个文件时,在request对象中的request.files中可以得到相应的上传文件。其中是一个类dict的对象,存放多个文件对象属性,例如, <input type="file" name="docfile">
定义了一个docfile字段,当上传后,可以通过:
filename = request.files['docfile'].filename
file_obj = request.files['docfile'].stream
来分别处理上传的文件名和文件对象。后续你要保存到哪个目录,同时考虑到中文的问题, 你可能需要将上传的unicode文件名转为与操作系统对应的本身编码。因此,为了解决这些 问题,Uliweb提供了upload app来处理上传文件。同时upload app还提供上传后文件的访 问处理,包括X-Sendfile的支持。
upload 的安装与配置¶
在apps/settings.ini中的INSTALLED_APPS中添加:
'uliweb.contrib.upload'
upload缺省提供以下配置:
[UPLOAD]
TO_PATH = './uploads'
BUFFER_SIZE = 4096
FILENAME_CONVERTER =
BACKEND =
#X-Sendfile type: nginx, apache
X_SENDFILE = None
#if not set, then will be set a default value that according to X_SENDFILE
#nginx will be 'X-Accel-Redirect'
#apache will be 'X-Sendfile'
X_HEADER_NAME = ''
X_FILE_PREFIX = '/files'
[EXPOSES]
file_serving = '/uploads/<path:filename>', 'uliweb.contrib.upload.file_serving'
其中:
- TO_PATH
为文件要保存的目录。缺省为当前路径下的uploads子目录。一般,当前路径就是你的项目目录。
- BUFFER_SIZE
保存文件时的块大小。
- FILENAME_CONVERTER
文件名转換类,它用来处理当文件上传后,在保存文件时使用的文件名。没有给出此配置时缺省是使用UUID来生成文件名,以保证文件名不重复。同时upload还定义了其它的几种转換类,可以根据需要来使用。更详细的说明参见下面具体的说明。
- BACKEND
upload中文件上传和下载的类定义。如果没有给出,则缺省使用
FileServing
类来处理。
目前upload还支持X-Sendfile的处理方式,这是目前apache, nginx中都有的一种方法,不过细节上有所差异。关于X-Sendfile这里有一篇 文章 可以参考。简单地说就是在下载文件时可以对下载的过程进行控制,详情可参加下面的Nginx配置示例。
- X_SENDFILE
X-Sendfile处理类型,目前只支持Nginx和Apache。根据需要可以输入'nginx'或'apache'。缺省为None,则表示不启动,则文件读取及下载是由Uliweb本身提供的。
- X_HEADER_NAME
当X_SENDFILE生效时,此选项用于指明将返回web server的头。目前已经知道Nginx和Apache要使用的头标识,其它的web server可以在这里指定。当保持为空时,则根据X_SENDFILE的值自动使用相应的头标识,对于Nginx则为 X-Accel-Redirect ,对于Apache则为 X-Sendfile。
- X_FILE_PREFIX
传给web server的头中新的URL的前缀。因为这个URL与原始的URL将不一样,所以利用这个前缀可以方便生成新的URL。这个前缀需要与web server中的内部URL相对应。不过对于Nginx和Apache的处理机制不完全相同,对于Nginx的方式,是通过返回一个新的URL,而对于Apache来说,则需要返回一个文件路径,不是一个URL。所以这个项的设置要根据所使用的web server而有所不同。对于Apache则可以认为是目录的前缀。对于Nginx则可以认为是新的URL的前缀。
- EXPOSES/file_serving
用于定义一个缺省的View函数,以处理上传后的文件的下载。
其实这个配置中,真正与上传有关的就是前两项。后几项都是和下载有关的。在upload中,不仅处理了上传还为了方便处理了下载。只不过,它与静态文件的下载不同,它的下载是可以在非static目录下,并且可以有view的控制参与的。而静态文件是不需要进行控制处理的。
文件上传的处理¶
其实在Uliweb有不同级别的文件上传处理,比如最原始的就是手工处理、然后就是利用upload来处理、再有就是通过generic.py来处理。在处理时,有使用Form来处理的,也可以不使用Form来处理,而是使用request,它们之间有一些差异。generic.py会专门在generic.py的文档进行讲解,这里将根据几种情景进行说明。
上传Form的定义¶
其实使用手工HTML或利用Uliweb提供的Form类来生成Form代码都没有太大关系,基本上是一样的。 简单的话,就是使用Form类了,例如:
from uliweb.form import *
class F(Form):
file = FileField(label='file')
@expose('/show_upload')
def show_upload():
form = F(action='/upload')
return {'form':form}
上面定义了一个Form类,然后我们在show_upload()中将返回一个dict,用于模板的渲染。这个view方法只处理了显示,上传还没有处理。在F创建时,我们传入action的值用于指定上传文件后的处理URL。
不使用upload app进行上传处理¶
当用户选择了文件,并提交上传后,信息将提交到/upload来。则对应的处理代码示例为:
import os
@expose('/upload')
def upload():
form = F(action='/upload')
if form.validate(request.params, request.files):
filename = request.files['file'].filename
target = os.path.join('./uploads', filename)
with open(target, 'wb') as f:
f.write(request.files['file'].stream.read())
return redirect('/ok')
else:
#指定将要使用的模板文件名
response.template = 'show_upload.html'
#如果校验失败,则再次返回Form,将带有错误信息
return {'form':form}
先生成保存目标的文件名,然后手工将上传的内容进行保存。不过,这里如果文件名有中文有可能会报错。request中得到的文件名是unicode,你需要将其转为与操作系统相匹配的编码。在Uliweb的全局配置项中提供了一个:
[GLOBAL]
FILESYSTEM_ENCODING = None
你可以考虑先对其进行配置,然后使用它来处理文件的编码。因此,你需要做的处理主要就是:
- 生成目标文件名(可能要处理文件名编码的问题)
- 保存文件
下面再看一看使用upload app的做法
使用upload app进行上传处理¶
首先安装upload app。
然后设置配置项,比如TO_PATH的值,缺省是./uploads。
将上面的代码修改一下:
import os
@expose('/upload')
def upload():
from uliweb import functions
form = F(action='/upload')
if form.validate(request.params, request.files):
functions.save_file(form.file.data.filename, form.file.data.file)
return redirect('/ok')
else:
#指定将要使用的模板文件名
response.template = 'show_upload.html'
#如果校验失败,则再次返回Form,将带有错误信息
return {'form':form}
这里使用了upload中提供的save_file函数,它的原型为:
save_file(filename, fobj, replace=False, convert=True)
这里只提供了两个参数,一个是文件名,一个是文件对象。第三个没有提供,因此如果存在 同名的文件,将不会覆盖,而是自动添加象(1), (2)这样的内容。在save_file中会自动根 据相关的配置项:文件系统编码、保存目录信息来自动生成目标文件名并转換成合适的编码, 然后保存。第4个参数用来控制是否自动进行文件名转換,缺省会转換。其目的是为了让文件 名不重复,没有乱码。
这里save_file是通过functions对象来引用的。在upload中还定义了其它类似的通用方法, 后面我们会看到。
为了方便处理Form字段,upload app还提供了save_file_field函数,具体使用参见下面的 函数说明。
放在一起的处理方式¶
我们可以考虑把显示和上传后的处理放在一起,也可以象这个例子一样,分开不同的URL。如果放在一起,逻辑可以是:
def upload():
from uliweb import functions
form = F()
#GET是显示用,POST是提交用
if request.method == 'GET':
return {'form':form}
else:
#如果提交,则先进行校验,这里是使用Form的方式
#form有一个validate方法,可以传入多个值,这里将request.files传入
#以便形成完整的数据集,如果validate返回True,表示校验成功,并且
#上传的数据将按照Form字段定义的类型已经做了转換
if form.validate(request.params, request.files):
functions.save_file(form.file.data.filename, form.file.data.file)
return redirect('/ok')
else:
#如果校验失败,则再次返回Form,将带有错误信息
return {'form':form}
FileServing 类¶
upload 把文件和下载的管理组织成了类的形式。这个类就是FileServing,你可以根据需要 从这个类进行派生。在缺省情况下,upload app会自动创建一个default_fileserving,而 前面所看到的UPLOAD的配置项就是这个缺省的文件服务类。同时,基于这个缺省的实例,提 供了下面的一些方法。在简单的情况下,你可以只使用缺省的文件服务对象就够了。
FileServing的说明:
class FileServing(object):
default_config = 'UPLOAD'
options = {
'x_sendfile' : ('X_SENDFILE', None),
'x_header_name': ('X_HEADER_NAME', ''),
'x_file_prefix': ('X_FILE_PREFIX', '/files'),
'to_path': ('TO_PATH', './uploads'),
'buffer_size': ('BUFFER_SIZE', 4096),
'_filename_converter': ('FILENAME_CONVERTER', None),
}
#每个FileServing类有相应的settings配置项。因此FileServing的所有方法
#都是根据这些配置项计算来的
#options中的每个值后面都可以使用 obj.xxx 来访问
#
#default_config表示会从哪里读配置信息。它会和下面的配置项合成,如,可以
#和TO_PATH合成为 UPLOAD/TO_PATH 。如果配置项中间有 '/' ,则认为不要进行合成。
def __init__(self, default_filename_converter_cls=UUIDFilenameConverter, config=None):
"""
初始化函数。第一个参数表示文件名转換类,缺省使用UUID方式生成文件名
第二个参数表示读取配置项的名字,与Settings.ini中的section对应
"""
def filename_convert(self, filename):
"""
对文件名进行转換
"""
def get_filename(self, filename, filesystem=False, convert=False)
"""
用于获得一个文件的实际路径。它是根据to_path计算得到的。如果
filesystem为True,则会将生成的文件名按settings中配置的文件
系统编码来进行转換。convert参数用于处理是否要进行文件名的转換。
因此根据参数的不同,它有几种用法:
1. 根据传入的filename得到对应的实际路径,但文件名不转換为文件系统
的编码:
get_filename(filename)
2. 根据传入的filename得到对应的实际路径,但是文件名转換为文件系统
的编码:
get_filename(filename, filestystem=True)
3. 得到filiename的实际路径,同时进行文件名转換,这样得到的文件名将
不是原来的文件名:
get_filename(filename, convert=True)
前两种主要是用在上传文件后的显示上,这时一般使用的是转換后的文件名。
第三种是用在上传后保存文件时,先对文件名进行转換。
"""
def download(self, filename, action='download', x_filename='', real_filename='')
"""
提供下载处理,支持X-Sendfile的处理。action取值为'download'或
'inline',它们分别对应不同的应答头:
download
Content-Disposition:attachment; filename=<filename>
inline
Content-Disposition:inline; filename=<filename>
如果action为None,则不显示上面的头信息。
在这里,我们看到有三个文件名,都有什么用?
filename一般是从数据库中取出来的文件名,比如我们将文件名保存到
FileProperty中,当取出来时是Unicode格式的,并且是相对于上传路径
的相对路径,所以我们要进行转換。
如果不考虑X-Sendfile的情况,一般我们只提供filename就足够了,因
为可以自动根据to_path来计算出实际文件路径。不过当文件名并不存在
于to_path所指定的目录下时,我们还可以提供real_filename参数来指
明文件实际的路径。
对于使用了X-Sendfile的情况,又复杂了一些。我们可能还需要指出
x_filename参数,比如在nginx下,它用来指明X-Accel-Redirect中的
文件名,而这个文件路径是一个URL,提供Nginx可以找到真正的文件。
所以x_sendfile其实是一个中间路径。
所以x_sendfile和real_filename其实不会同时使用。在更底层的filedown
函数中会进行确实的处理。对于用户来说,如果想实现根据配置不同,
使用不同的下载方式,则么这些参数最好都提供。
"""
def save_file(self, filename, fobj, replace=False, convert=True)
"""
将文件保存在to_path路径下。
使用convert可以设置要不要转換文件名。
"""
def save_file_field(self, field, replace=False, filename=None, convert=True)
"""
根据文件字段来保存。路径处理同save_file
"""
def save_image_field(self, field, resize_to=None, replace=False, filename=None, convert=True)
"""
根据图片字段来保存。路径处理同save_file
"""
def delete_filename(self, filename)
"""
删除保存在to_path下的文件。
"""
def get_href(self, filename)
"""
获取filename对应的URL地址,不是真正的URL信息
"""
def get_url(self, filename, query_para=None, **url_args)
"""
获取filename对应的URL。注意,这是一个真正的URL,如果只是想得到URL的
地址,要使用get_href(filename)
如果url_args中传入了 title 和 text,则生成的URL形式为
<a href='xxx' title='title'>text</a>
如果没有传入,则使用filename代替title和text。
如果传入了query_para,则它的值将写在href对应的链接后面。query_para
是一个dict值,如: ``query_para={'alt':'filename.txt'}``
那么生成的URL可能为:
<a href='xxx?alt=filename.txt' title='title'>text</a>
它有什么用,在后面的download你会看到
"""
自定义FileServing类¶
如果需要自定义FileServing类,可以从这个类派生。如果没有新増的配置项,只要定义 default_config
即可。它表示一个完整的section,用来定义options中所有的值。因此 我们需要在settings.ini中定义完整的配置信息,可以直接拷贝UPLOAD的定义。如果有些值 想采用UPLOAD的值,并且希望和UPLOAD值一起变化,那么可以在定义值的时候引用UPLOAD的值,如:
[UPLOAD_TEST]
TO_PATH = UPLOAD.TO_PATH
BUFFER_SIZE = UPLOAD.BUFFER_SIZE
FILENAME_CONVERTER =
BACKEND =
X_SENDFILE = None
X_HEADER_NAME = ''
X_FILE_PREFIX = '/files'
上面前两项使用了UPLOAD中的定义值。
简单示例:
class FileServingTest(FileServing):
default_config = 'UPLOAD_TEST'
如何获取FileServing对象¶
想要获得FileServing实例,可以先导入这个类,然后创建它的实例。创建时可以传入对应的 配置section的名字,以获得完整的配置,否则使用类中定义的配置。
另一种方法是使用 get_fileserving(config)
函数。它会使用FileServing作为BACKEND类,但 是配置项使用你指定的section的名字。这样不会创建新的类,但是实例的参数是可以不同。
upload app提供方法说明¶
以下方法都是基于缺省的default_fileserving对象来处理的。
- get_fileserving(config)
获得缺省的文件上传下载对象。缺省使用UPLOAD的配置。如果传入其它的配置名,将 使用这个配置来创建FileServing对象。
- file_serving(filename)
缺省的文件下载函数。它是通过在 settings.ini 中配置了:
[EXPOSES] file_serving = '/uploads/<path:filename>', 'uliweb.contrib.upload.file_serving'
这样所有以
/uploads
开头的 URL都会被file_serving
处理,从而提供服务。在这里还有特殊的扩展处理。在缺省情况下,上传后的文件为了保证唯一性会自动 对文件名进行转換,具体用什么要看使用哪个文件名生成器处理的。详见下面
FilenameConverter
的有关说明。因此,当下载文件名还是使用转換后的文件 名会非常不方便。所以这里有一个扩展,就是在传入的URL上添加一个特殊的 query_string,如:xxxxxxxxx.txt?alt=中文.txt
这样alt对应的就是想另存为的文件名。这样只要
<a>
标签加上alt
信息就可以 以想要的文件名来保存。- get_filename(filename, filesystem=False, convert=False)
用于获得目标文件,即将TO_PATH与filename进行连接。同时,如果给出filesystem为 True,则将文件名转为文件系统的编码。否则返回的将是unicode。 convert=False 表示不对文件名进行转換
- save_file(filename, fobj, replace=False, convert=True)
用于保存一个文件。需要传入文件名和文件对象,这些都可以从request或form字段中 获得。如果replace设置为True,则表示当存在同名文件时自动覆盖,否则将自动添加 (1), (2)等内容,以保证文件不重名。save_file会把文件保存到指定的目录下,并根 据配置项进行相应的文件名编码的转換。
- save_file_field(field, replace=False, filename=None, convert=True)
用于处理Form中的FileField字段。将自动从FileField中获得对应的文件名和文件对象。 也可以将文件保存为filename参数指定的文件名。
- save_image_field(field, resize_to=None, replace=False, filename=None, convert=True)
和save_file_field类似,是用来处理ImageField(图像字段)的。不过,如果你设置了 resize_to参数的话,它还可以自动对图像进行缩放处理。
- delete_filename(filename)
删除上传目录下的某个文件。
- get_url(filename, query_para=None, **url_args)
获得上传目录下某个文件的URL,以便可以让浏览器进行访问。 query_para 将传入到href属性后面成为query_string.
- get_href(filename, **kwargs)
获取filename对应的URL地址,不是真正的URL信息
如果上面的文件名使用的是相对路径,则会根据当前的FileServing对象来决定使用 什么配置信息,比如文件保存的路径。但是如果使用绝对路径,则将使用绝对路径进 行处理。
FilenameConverter类的说明¶
upload提供了几个用于文件名上传后转换的类,并且可以在settings.ini进行配置,分别说明如下:
FilenameConverter:
class FilenameConverter(object): @staticmethod def convert(filename): return filename
最基本的类,对文件名不作任何转換
- UUIDFilenameConverter 使用UUID方法生成文件名。文件名将保证唯一。
MD5FilenameConverter 使用MD5算法生成文件名。具体算法:
f = md5( md5("%f%s%f%s" % (time.time(), id({}), random.random(), getpid())).hexdigest(), ).hexdigest()
BACKEND配置说明¶
upload在启动时会缺省按照 UPLOAD/BACKEND
的定义来生成缺省的fileserving类。如果 没给出,则使用FileServing。通过修改 BACKEND
,用户就可以定义自已缺省的文件上传处理类。
X-Sendfile Nginx配置说明¶
简单的处理流程可以表示为:
以上的处理可以理解为:
- 用户请求的url在后台经过处理后,由后台处理添加一个内部的头信息,头信息带有一个新的URL,并且返回内容为空,因此真正的内容将由Nginx完成,所以只要添加相应的头信息即可。同时你也可能会返回其它的头信息,如:
'Content-Disposition'
,'Content-Type'
等。 - Nginx在发现
'X-Accel-Redirect'
头之后会自动删除,并且根据URL的信息去对应的目录下查找相应的文件,然后返回。 - 因此用户看到的文件路径有可能和真正存放文件的路径不同。并且,允许后台处理根据需要来决定返回
'X-Accel-Redirect'
还是其它的信息,从而可以控制是否真正进行文件下载。一方面可以进行下载控制,另一方面可以对后台文件进行保护。
Nginx的配置如下:
location /files {
internal;
alias /path/to/files;
}
在Nginx的conf文件中添加上面的内容,需要根据需要进行修改。其中:
- /files
为你将在后台处理中要重新生成的URL的前缀。
- internal
表示内部使用,用户将无法直接通过URL来访问这个路径。
- alias
指明/files后对应的文件信息存放的路径。这里还可以考虑使用root,它们的区别就是:
例如URL为 /files/filename,如果配置为 alias /download,则将要读取的文件应该是 /download/filename,而如果配置为 root /download,则将要读取的文件将是 /download/files/filename
启用Nginx进行文件下载处理的配置项应设置为:
[UPLOAD]
X_SENDFILE = 'nginx'
functions定义说明¶
为了方便使用,在settings.ini中的FUNCTIONS中定义了一些全局函数,可以方便使用:
- get_filename
- save_file
- save_file_field
- save_image_field
- delete_filename
- get_url
- get_href
- download
- filename_convert
- get_fileserving