生成大文件并发送
Generate large file and send it
我有一个相当大的 .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)
我用上面的方法遇到的问题是:
- Web 浏览器不会直接开始下载已发送的块。它在网络服务器似乎准备好全部内容时挂起。
- Web 服务器在处理此请求时被阻止并导致其他客户端挂起。
对于你的第一个问题,你需要flush()
给定的块到输出缓冲区。
来自 documentation(粗体表示强调):
RequestHandler.write(chunk)[source]
Writes the given chunk to the output buffer.
To write the output to the network, use the flush() method below.
关于您的应用程序挂起,您正在处理来自主线程的请求,因此一切都将等待您的操作完成。您应该改为使用 Tornado 的 iostream
来进行此操作。来自 tornado.iostream
documentation:
tornado.iostream — Convenient wrappers for non-blocking sockets
Utility classes to write to and read from non-blocking files and sockets.
默认情况下,所有数据都缓存在内存中,直到请求结束,以便在发生异常时可以将其替换为错误页面。要增量发送响应,您的处理程序必须是异步的(因此它可以与响应的写入和 IOLoop 上的其他请求交错)并使用 RequestHandler.flush()
方法。
注意"being asynchronous"与"using the @tornado.web.asynchronous
decorator"不一样;在这种情况下,我建议使用 @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() 之前执行所有可能失败的操作。
我有一个相当大的 .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)
我用上面的方法遇到的问题是:
- Web 浏览器不会直接开始下载已发送的块。它在网络服务器似乎准备好全部内容时挂起。
- Web 服务器在处理此请求时被阻止并导致其他客户端挂起。
对于你的第一个问题,你需要
flush()
给定的块到输出缓冲区。来自 documentation(粗体表示强调):
RequestHandler.write(chunk)[source]
Writes the given chunk to the output buffer.
To write the output to the network, use the flush() method below.
关于您的应用程序挂起,您正在处理来自主线程的请求,因此一切都将等待您的操作完成。您应该改为使用 Tornado 的
iostream
来进行此操作。来自tornado.iostream
documentation:tornado.iostream — Convenient wrappers for non-blocking sockets Utility classes to write to and read from non-blocking files and sockets.
默认情况下,所有数据都缓存在内存中,直到请求结束,以便在发生异常时可以将其替换为错误页面。要增量发送响应,您的处理程序必须是异步的(因此它可以与响应的写入和 IOLoop 上的其他请求交错)并使用 RequestHandler.flush()
方法。
注意"being asynchronous"与"using the @tornado.web.asynchronous
decorator"不一样;在这种情况下,我建议使用 @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() 之前执行所有可能失败的操作。