我继承了使用Heroku部署的Rails应用程序(我认为)。我在AWS的Cloud9 IDE上对其进行编辑,现在,仅在开发模式下进行所有操作。该应用程序的目的是处理大量调查数据并将其吐出到PDF报告中。这适用于包含10行数据的小型报告,但是当我加载查询5000多个行的数据以创建HTML页面并将其转换为PDF的报告时,该报告大约需要105秒,比Heroku的要长得多为HTTP请求分配的30秒。
Heroku在他们的网站上这样说,这给了我一些希望:
“ Heroku支持HTTP 1.1功能,例如长轮询和流式响应。应用程序具有最初的30秒窗口,用于以单个字节返回给客户端。但是,此后传输的每个字节(从客户端接收还是由您的应用程序发送)重置滚动的55秒窗口。如果在55秒的窗口中未发送任何数据,则连接将终止。” (来源:https://devcenter.heroku.com/articles/request-timeout#long-polling-and-streaming-responses)
这对我来说听起来很棒-我可以每秒大约在一个循环中向客户端发送一个请求,直到完成创建大型PDF报告为止。但是,我不知道如何发送或接收一个字节左右来“重置”他们正在谈论的55秒窗口。
这是控制器中发送请求的部分。
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}
我正在提出其他要求以达到这一点,但是我认为导致问题的部分是在此处渲染模板的地方。我的模板以有限循环查询数据库,当它用完要查询的调查问题时,该循环将停止。
我的问题是:我如何“发送或接收一个字节给客户端”以告诉Heroku“我仍在尝试创建此庞大的PDF,所以请重置计时器并给我55秒!”是查询形式吗?因为,如果是这样,我将在report.html.erb文件中一遍又一遍地查询MySql数据库。
它过去也可以正常工作,并且可以处理小型报表,但是现在在实际页面上的请求完成之前,我收到了“ 504 Gateway Timeout”错误,但是我的puma控制台仍然像疯子我认为这是一个Heroku问题,因为504错误恰好每35秒发生一次(处理其他部分需要5秒,尝试完成模板中的循环需要30秒才能正确呈现)。
如果您需要更多信息或代码,请询问!预先感谢
编辑: 下面的两条注释都建议可能的重复项,但是它们都没有使用实际代码给出真正的答案,它们只是引用了我在这里引用的文档。我正在寻找一个代码示例(或者至少是一种进入我的门的方法),而不仅仅是指向文档的链接。谢谢!
编辑2:
我尝试了@Sergio所说的话,并安装了SideKiq。我想我真的很亲密,但与工人仍有一些问题。工作人员无权访问Rails中的render方法所需的ActionView :: Base,因此它无法正常工作。我可以访问worker方法,这意味着我的sidekiq和Redis服务器正在正确运行,但是由于以下错误而被捕获在ActionView行中:
WARN: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
):
我不知道当它说“有时它只是用--load-error-handling ignore忽略该错误”时的含义。该文件肯定存在,并且我尝试了文件路径的5种变化。
答案 0 :(得分:1)
我不得不做几次这样的事情。在所有情况下,我最终都写了一份后台工作,完成了所有的 lifting 工作。并且由于它不是Web请求,因此不受30秒超时的影响。它是这样的:
以上方案使用短轮询。我发现它最容易实现。但这当然在资源上有点浪费。您可以使用长轮询或网络套接字或其他精美的东西。
答案 1 :(得分:0)
检查 my response here 以防万一它对您有用。我不想更改用户工作流程,添加一个 bg 作业,然后添加一个位置/通知来获得结果。 我使用带有 Live 模块的 Rails 控制器流支持并设置正确的响应头。我从某个 Enumerable 对象中获取数据。