在Django中提供大内容(或大文件)下载

Django 框架建站很方便,不过有时也会遇到一些麻烦,比如至少到目前的版本中它都还没有提供 flush 方法,有时需要向客户端输出一个非常大的内容,怎么处理呢?

如果这个非常大的内容是一个文件,最简单的办法就是使用静态文件,让 Apache 等服务器来处理大文件的下载。不过有时,我们可能需要在下载之前先检查一下用户的权限,可能不能向用户暴露文件的真实地址,或者这个大内容是临时生成的(比如临时将多个文件合并而成的),这时就不能使用静态文件了。

在 PHP 甚至 ASP 中,都有 flush 或类似方法,比如 PHP 中可以这样做:

do_something();
print 'Done doing something!';
flush();

do_something_else();
print 'Done doing something else!';
flush();

但 Django 中还没有实现对应的方法。一个解决办法是先将要传送的内容全生成在内存中,然后再一次性传入 HttpResponse,比如:

def bigFileView(request):
	# do something...
	c = open("big_file.txt", "rb").read()
	return HttpResponse(c)

这样处理最简单粗暴,但也存在很大的问题,如果这个文件非常大,这样处理可能会占用大量的内存,甚至导致服务器崩溃。

不过 Django 的开发者应该也考虑到了这种情况,官方文档中提到,可以给 HTTPResponse 传入一个迭代器,同时,StackOverflow 上也有一个讨论这个问题的页面。根据这两个页面的内容,上面这个下载大文件的问题可以这样解决:

def bigFileView(request):
	# do something...

	def readFile(fn, buf_size=262144):
		f = open(fn, "rb")
		while True:
			c = f.read(buf_size)
			if c:
				yield c
			else:
				break
		f.close()

	file_name = "big_file.txt"
	response = HttpResponse(readFile(file_name))

	return response

虽然这和其它语言中的 flush 不太一样,并且也并不是每次 yield 时客户端都会收到相应的内容(可能是因为服务器或客户端的缓冲),但这样的方法的确可以解决大文件或大内容下载的问题。经测试,使用这样的方法下载大文件时,服务器的内存占用几乎没有变化。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s