如何绕过 Heroku 的 HTTP 30 秒限制?
How can I get around Heroku's HTTP 30 second limit?
我继承了一个使用 Heroku 部署的 rails 应用程序(我认为)。我在 AWS 的 Cloud9 IDE 上编辑它,现在,只需在开发模式下进行所有操作。该应用程序的目的是处理大量调查数据并将其输出到 PDF 报告中。这适用于包含 10 行数据的小型报告,但是当我加载一个查询 5000 多行数据上传的报告以创建一个 HTML 页面并转换为 PDF 时,大约需要 105 秒,比 Heroku 分配给 HTTP 请求的 30 秒长得多。
Heroku 在他们的网站上这样说,这给了我一些希望:
"Heroku supports HTTP 1.1 features such as long-polling and streaming responses. An application has an initial 30 second window to respond with a single byte back to the client. However, each byte transmitted thereafter (either received from the client or sent by your application) resets a rolling 55 second window. If no data is sent during the 55 second window, the connection will be terminated."(来源:https://devcenter.heroku.com/articles/request-timeout#long-polling-and-streaming-responses)
这对我来说听起来很棒 - 我可以每隔一秒左右循环向客户端发送一个请求,直到我们完成创建大型 PDF 报告。但是,我不知道如何向他们正在谈论的 "reset the rolling 55 second window" 发送或接收一个字节左右。
这是我的控制器发送请求的部分。
return render pdf: pdf_name + " " + pdf_year.to_s,
disposition: 'attachment',
page_height: 1300,
encoding: 'utf8',
page_size: 'A4',
footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
margin: { top: 10, # default 10 (mm)
bottom: 20,
left: 10,
right: 10 },
template: "recent_grad/report.html.erb",
locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}
我正在提出其他请求以达到这一点,但我相信导致问题的部分是在呈现模板的地方。我的模板在一个有限循环中查询数据库,当它用完要查询的调查问题时停止。
我的问题是这样的:我如何"send or receive a byte to the client"告诉Heroku "I'm still trying to create this massive PDF so please reset the timer and give me my 55 seconds!"是不是以查询的形式?因为,如果是这样,我将在我的 report.html.erb 文件中一遍又一遍地查询 MySql 数据库。
此外,它过去可以正常工作并且可以处理小报告,但现在我在实际页面上完成请求之前收到错误“504 网关超时”,但我的 puma 控制台继续查询数据库像个疯子。我认为这是一个 Heroku 问题,因为 504 错误恰好每 35 秒发生一次(5 秒处理其他部分,30 秒尝试完成模板中的循环,以便它可以正确呈现)。
如果您需要更多信息或代码,请询问!提前致谢
编辑:
下面的两条评论都提出了可能的重复,但它们都没有真正的代码答案,它们只是引用了我在这里引用的文档。我正在寻找一个代码示例(或者至少是一种让我踏入大门的方法),而不仅仅是文档的 link。谢谢!
编辑 2:
我尝试了@Sergio 所说的并安装了 SideKiq。我想我真的很接近,但仍然与工作人员有一些问题。工作人员无权访问 rails 中的渲染方法所需的 ActionView::Base,因此它无法正常工作。我可以访问 worker 方法,这意味着我的 sidekiq 和 redis 服务器是 运行 正确的,但它在 ActionView 行上被捕获并出现此错误:
警告:NameError:未初始化常量HardWorker::ActionView
这是工人代码:
require 'sidekiq'
Sidekiq.configure_client do |config|
# config.redis = { db: 1 }
config.redis = { url: 'redis://172.31.6.51:6379/0' }
end
Sidekiq.configure_server do |config|
# config.redis = { db: 1 }
config.redis = { url: 'redis://172.31.6.51:6379/0' }
end
class HardWorker
include Sidekiq::Worker
def perform(pdf_name, pdf_year)
av = ActionView::Base.new()
av.view_paths = ActionController::Base.view_paths
av.class_eval do
include Rails.application.routes.url_helpers
include ApplicationHelper
end
puts "inside hardworker"
puts pdf_name, pdf_year
av.render pdf: pdf_name + " " + pdf_year.to_s,
disposition: 'attachment',
page_height: 1300,
encoding: 'utf8',
page_size: 'A4',
footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
margin: { top: 10, # default 10 (mm)
bottom: 20,
left: 10,
right: 10 },
template: "recent_grad/report.html.erb",
locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}
end
end
有什么建议吗?
编辑 3:
我做了@Sergio 所说的并尝试直接从 html.erb 文件制作 PDF 并将其保存到文件中。这是我的代码:
# /app/controllers/recentgrad_controller.rb
pdf = WickedPdf.new.pdf_from_html_file('home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb')
save_path = Rails.root.join('pdfs', pdf_name + pdf_year.to_s + '.pdf')
File.open(save_path, 'wb') do |file|
file << pdf
end
并且错误输出:
RuntimeError (Failed to execute:
["/usr/local/rvm/gems/ruby-2.4.1@gradSurvey/bin/wkhtmltopdf", "file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb", "/tmp/wicked_pdf_generated_file20190523-15416-hvb3zg.pdf"]
Error: PDF could not be generated!
Command Error: Loading pages (1/6)
Error: Failed loading page file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb (sometimes it will work just to ignore this error with --load-error-handling ignore)
Exit with code 1 due to network error: ContentNotFoundError
):
我不知道 "sometimes it will work just to ignore this error with --load-error-handling ignore" 是什么意思。该文件肯定存在,我已经尝试了文件路径的 5 种变体。
我不得不多次这样做。在所有情况下,我最终都编写了一个后台作业来完成所有繁重的 lifting 生成。而且因为它不是 Web 请求,所以它不受 30 秒超时的影响。它是这样的:
- 客户(您的 javascript 代码)请求新报告。
- 服务器生成职位描述并将其排队供您的员工提取。
- 工作人员从队列中挑选工作并开始工作(查询数据库等)
- 与此同时,客户端定期询问服务器"is my report done yet?"。服务器响应 "not yet, try again later"
- 工作人员已完成生成报告。它将文件上传到某个存储(例如 S3),将作业状态设置为 "completed",将作业结果设置为下载 link 以上传报告文件。
- 服务器,看到作业已完成,现在可以响应客户端状态更新请求"yes, it's done now. Here's the url. Have a good day."
- 大家都很开心。没有人需要做任何流媒体或玩 heroku 的滚动响应超时。
以上场景使用short-polling。我发现它最容易实现。但是,当然,这在资源方面有点浪费。您可以使用 long-polling 或 websockets 或其他花哨的东西。
检查 my response here 以防它适合您。我不想更改用户工作流程,添加一个 bg 作业,然后添加一个 place/notification 以获得结果。
我将 Rails 控制器流支持与 Live 模块一起使用,并设置正确的响应 headers。我从一些 Enumerable object.
中获取数据
我继承了一个使用 Heroku 部署的 rails 应用程序(我认为)。我在 AWS 的 Cloud9 IDE 上编辑它,现在,只需在开发模式下进行所有操作。该应用程序的目的是处理大量调查数据并将其输出到 PDF 报告中。这适用于包含 10 行数据的小型报告,但是当我加载一个查询 5000 多行数据上传的报告以创建一个 HTML 页面并转换为 PDF 时,大约需要 105 秒,比 Heroku 分配给 HTTP 请求的 30 秒长得多。
Heroku 在他们的网站上这样说,这给了我一些希望:
"Heroku supports HTTP 1.1 features such as long-polling and streaming responses. An application has an initial 30 second window to respond with a single byte back to the client. However, each byte transmitted thereafter (either received from the client or sent by your application) resets a rolling 55 second window. If no data is sent during the 55 second window, the connection will be terminated."(来源:https://devcenter.heroku.com/articles/request-timeout#long-polling-and-streaming-responses)
这对我来说听起来很棒 - 我可以每隔一秒左右循环向客户端发送一个请求,直到我们完成创建大型 PDF 报告。但是,我不知道如何向他们正在谈论的 "reset the rolling 55 second window" 发送或接收一个字节左右。
这是我的控制器发送请求的部分。
return render pdf: pdf_name + " " + pdf_year.to_s,
disposition: 'attachment',
page_height: 1300,
encoding: 'utf8',
page_size: 'A4',
footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
margin: { top: 10, # default 10 (mm)
bottom: 20,
left: 10,
right: 10 },
template: "recent_grad/report.html.erb",
locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}
我正在提出其他请求以达到这一点,但我相信导致问题的部分是在呈现模板的地方。我的模板在一个有限循环中查询数据库,当它用完要查询的调查问题时停止。
我的问题是这样的:我如何"send or receive a byte to the client"告诉Heroku "I'm still trying to create this massive PDF so please reset the timer and give me my 55 seconds!"是不是以查询的形式?因为,如果是这样,我将在我的 report.html.erb 文件中一遍又一遍地查询 MySql 数据库。
此外,它过去可以正常工作并且可以处理小报告,但现在我在实际页面上完成请求之前收到错误“504 网关超时”,但我的 puma 控制台继续查询数据库像个疯子。我认为这是一个 Heroku 问题,因为 504 错误恰好每 35 秒发生一次(5 秒处理其他部分,30 秒尝试完成模板中的循环,以便它可以正确呈现)。
如果您需要更多信息或代码,请询问!提前致谢
编辑: 下面的两条评论都提出了可能的重复,但它们都没有真正的代码答案,它们只是引用了我在这里引用的文档。我正在寻找一个代码示例(或者至少是一种让我踏入大门的方法),而不仅仅是文档的 link。谢谢!
编辑 2:
我尝试了@Sergio 所说的并安装了 SideKiq。我想我真的很接近,但仍然与工作人员有一些问题。工作人员无权访问 rails 中的渲染方法所需的 ActionView::Base,因此它无法正常工作。我可以访问 worker 方法,这意味着我的 sidekiq 和 redis 服务器是 运行 正确的,但它在 ActionView 行上被捕获并出现此错误:
警告:NameError:未初始化常量HardWorker::ActionView
这是工人代码:
require 'sidekiq'
Sidekiq.configure_client do |config|
# config.redis = { db: 1 }
config.redis = { url: 'redis://172.31.6.51:6379/0' }
end
Sidekiq.configure_server do |config|
# config.redis = { db: 1 }
config.redis = { url: 'redis://172.31.6.51:6379/0' }
end
class HardWorker
include Sidekiq::Worker
def perform(pdf_name, pdf_year)
av = ActionView::Base.new()
av.view_paths = ActionController::Base.view_paths
av.class_eval do
include Rails.application.routes.url_helpers
include ApplicationHelper
end
puts "inside hardworker"
puts pdf_name, pdf_year
av.render pdf: pdf_name + " " + pdf_year.to_s,
disposition: 'attachment',
page_height: 1300,
encoding: 'utf8',
page_size: 'A4',
footer: {html: {template: 'recent_grad/footer.html.erb'}, spacing: 0 },
margin: { top: 10, # default 10 (mm)
bottom: 20,
left: 10,
right: 10 },
template: "recent_grad/report.html.erb",
locals: {start: @start, survey: @survey, years: @years, college: @college, department: @department, program: @program, emphasis: @emphasis, questions: @questions}
end
end
有什么建议吗?
编辑 3: 我做了@Sergio 所说的并尝试直接从 html.erb 文件制作 PDF 并将其保存到文件中。这是我的代码:
# /app/controllers/recentgrad_controller.rb
pdf = WickedPdf.new.pdf_from_html_file('home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb')
save_path = Rails.root.join('pdfs', pdf_name + pdf_year.to_s + '.pdf')
File.open(save_path, 'wb') do |file|
file << pdf
end
并且错误输出:
RuntimeError (Failed to execute:
["/usr/local/rvm/gems/ruby-2.4.1@gradSurvey/bin/wkhtmltopdf", "file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb", "/tmp/wicked_pdf_generated_file20190523-15416-hvb3zg.pdf"]
Error: PDF could not be generated!
Command Error: Loading pages (1/6)
Error: Failed loading page file:///home/ec2-user/environment/gradSurvey/gradSurvey/app/views/recent_grad/report.html.erb (sometimes it will work just to ignore this error with --load-error-handling ignore)
Exit with code 1 due to network error: ContentNotFoundError
):
我不知道 "sometimes it will work just to ignore this error with --load-error-handling ignore" 是什么意思。该文件肯定存在,我已经尝试了文件路径的 5 种变体。
我不得不多次这样做。在所有情况下,我最终都编写了一个后台作业来完成所有繁重的 lifting 生成。而且因为它不是 Web 请求,所以它不受 30 秒超时的影响。它是这样的:
- 客户(您的 javascript 代码)请求新报告。
- 服务器生成职位描述并将其排队供您的员工提取。
- 工作人员从队列中挑选工作并开始工作(查询数据库等)
- 与此同时,客户端定期询问服务器"is my report done yet?"。服务器响应 "not yet, try again later"
- 工作人员已完成生成报告。它将文件上传到某个存储(例如 S3),将作业状态设置为 "completed",将作业结果设置为下载 link 以上传报告文件。
- 服务器,看到作业已完成,现在可以响应客户端状态更新请求"yes, it's done now. Here's the url. Have a good day."
- 大家都很开心。没有人需要做任何流媒体或玩 heroku 的滚动响应超时。
以上场景使用short-polling。我发现它最容易实现。但是,当然,这在资源方面有点浪费。您可以使用 long-polling 或 websockets 或其他花哨的东西。
检查 my response here 以防它适合您。我不想更改用户工作流程,添加一个 bg 作业,然后添加一个 place/notification 以获得结果。 我将 Rails 控制器流支持与 Live 模块一起使用,并设置正确的响应 headers。我从一些 Enumerable object.
中获取数据