我有一个相当大的.csv文件(最多100万行),我想在浏览器请求时生成和发送。
我当前的代码是(除了我实际上没有生成相同的数据):
class CSVHandler(tornado.web.RequestHandler):
def get(self):
self.set_header('Content-Type','text/csv')
self.set_header('content-Disposition','attachement; filename=dump.csv')
self.write('lineNumber,measure\r\n') # File header
for line in range(0,1000000):
self.write(','.join([str(line),random.random()])+'\r\n') # mock data
app = tornado.web.Application([(r"/csv",csvHandler)])
app.listen(8080)
我对上述方法的问题是:
答案 0 :(得分:8)
默认情况下,所有数据都在内存中缓冲,直到请求结束,以便在发生异常时将其替换为错误页面。要以递增方式发送响应,您的处理程序必须是异步的(因此它可以与响应的写入和IOLoop上的其他请求交错)并使用RequestHandler.flush()
方法。
请注意,“异步”与“使用@tornado.web.asynchronous
装饰器”不同;在这种情况下,我建议使用@tornado.gen.coroutine
代替@asynchronous
。这允许您在每次刷新时简单地使用yield
运算符:
class CSVHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
self.set_header('Content-Type','text/csv')
self.set_header('content-Disposition','attachment; filename=dump.csv')
self.write('lineNumber,measure\r\n') # File header
for line in range(0,1000000):
self.write(','.join([str(line),random.random()])+'\r\n') # mock data
yield self.flush()
self.flush()
启动将数据写入网络的过程,yield
等待数据到达内核。这使得其他处理程序可以运行并且还可以帮助管理内存消耗(通过限制客户端下载速度可以提前多远)。在CSV文件的每一行之后刷新有点贵,因此您可能只想在每100或1000行之后刷新。
请注意,如果下载开始后出现异常,则无法向客户端显示错误页面;你只能在中途减少下载。尝试验证请求并在第一次调用flush()之前执行可能失败的所有操作。
答案 1 :(得分:1)
对于第一个问题,您需要flush()
给定输出缓冲区的块。
来自documentation(强调重点):
RequestHandler.write(chunk)[source]
将给定的块写入输出缓冲区。
要将输出写入网络,请使用下面的flush()方法。
关于您的应用程序挂起,您正在从主线程提供请求,因此所有内容都将等待您的操作完成。您应该使用Tornado的iostream
进行此操作。来自tornado.iostream
documentation:
tornado.iostream - 非阻塞套接字的便捷包装器 用于写入和读取非阻塞文件和套接字的实用程序类。