我遇到了类似的问题。我在我的 Sinatra 应用程序中有一个本质上是“批量下载”页面,我的客户端应用程序调用它来将数据导入浏览器中的本地 webSQL 数据库。
下载页面(我们称之为“GET /download”)查询 CouchDB 数据库以获取给定视图中的所有文档,而这部分过程(很像您的查询)需要很长时间。
通过使用 Sinatra 的流 API,我能够解决这个问题。如上所述,您可以通过在 30 秒时间结束之前在响应中发送至少一个 by 来“重置”Heroku 中的时钟。这会将时钟重置 55 秒(并且您发送的每个额外字节都会再次重置时钟),因此当您仍在发送数据时,连接可以无限期地保持打开状态。
在 Sinatra 中,我可以替换它:
get '/download' do
body = db.view 'data/all'
end
..用这样的东西:
get '/download' do
# stream is a Sinatra helper that effectively does 'chunked transfer encoding'
stream do |out|
# Long query starts here
db.view 'data/all' do |row|
# As each row comes back from the db, stream it to the front-end
out << row
end
end
end
这对我来说效果很好,因为“第一个字节的时间”(即 db 查询返回第一行所用的时间远低于 30 秒的限制。
唯一的缺点是之前我将所有结果从数据库中返回到我的 Sinatra 应用程序中,然后计算整个结果的 MD5 和以用作 etag 标头。我的客户端应用程序可以使用它来执行条件 HTTP 获取(即,如果它尝试再次下载并且没有修改任何数据,我可以发送 302 Not Modified);另外,它可以与它自己收到的数据的校验和进行比较(以确保它在传输过程中没有被损坏/修改)。
一旦您开始在响应中流式传输数据,您将无法计算要作为 HTTP 标头发送的内容的 MD5 总和(因为您还没有所有数据;并且您无法在之后发送标头)已经开始发送正文内容)。
我正在考虑将其更改为某种分页、多 AJAX 调用的解决方案;正如 Zenph 上面建议的那样。或者使用某种工作进程(例如 Resque、DelayedJob)将查询卸载到;但是我不确定当数据准备好获取时如何通知客户端。
希望这可以帮助。