如何克服Heroku的HTTP 30秒限制?

时间:2019-05-21 20:58:56

标签: ruby-on-rails heroku cloud9-ide puma

我继承了使用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种变化。

2 个答案:

答案 0 :(得分:1)

我不得不做几次这样的事情。在所有情况下,我最终都写了一份后台工作,完成了所有的 lifting 工作。并且由于它不是Web请求,因此不受30秒超时的影响。它是这样的:

  1. 客户端(您的JavaScript代码)请求新报告。
  2. 服务器会生成职位描述并将其排入队列,以供您的工作人员提取。
  3. 工作人员从队列中选择工作并开始工作(查询数据库等)
  4. 同时,
  5. 客户端定期询问服务器“我的报告完成了吗?”。服务器回复“尚未,请稍后再试”
  6. 工作人员已完成生成报告。它将文件上传到某个存储设备(例如S3),将作业状态设置为“已完成”,并将作业结果设置为已上传报告文件的下载链接。
  7. 服务器看到作业已完成,现在可以响应客户端状态更新请求:“是的,现在完成了。这是URL。祝您愉快。”
  8. 每个人都很高兴。而且没有人需要做任何流式传输或玩heroku的滚动响应超时。

以上方案使用短轮询。我发现它最容易实现。但这当然在资源上有点浪费。您可以使用长轮询或网络套接字或其他精美的东西。

答案 1 :(得分:0)

检查 my response here 以防万一它对您有用。我不想更改用户工作流程,添加一个 bg 作业,然后添加一个位置/通知来获得结果。 我使用带有 Live 模块的 Rails 控制器流支持并设置正确的响应头。我从某个 Enumerable 对象中获取数据。