【问题标题】:How can I use Sidekiq to render large PDFs as a background process to avoid Heroku's 30 second timeout for HTTP requests?如何使用 Sidekiq 将大型 PDF 渲染为后台进程,以避免 Heroku 对 HTTP 请求的 30 秒超时?
【发布时间】:2019-10-09 12:19:23
【问题描述】:

我不是专家,但我在谷歌上搜索和堆栈溢出的时间并没有给我答案,所以我决定提出自己的问题。

我正在使用 Ruby on Rails 5,我正在修补使用 AWS 上的 Cloud9 IDE。该应用程序是使用 Heroku 部署的,它对 HTTP 请求有 30 秒的超时。我需要从包含大量逻辑和查询的 html.erb 文件生成 PDF,因此通常需要大约 100 秒才能完成,并且由于它发生在控制器中,因此它被视为 HTTP 请求并且是需要不到 30 秒或作为后台进程完成。如果您知道解决 Heroku 30 秒 HTTP 请求超时的另一种方法,请告诉我。

我在另一篇文章中询问了这个问题并得到了反馈,我尝试使用带有 Rails 的 Sidekiq 之类的东西来处理庞大的进程,而不是尝试使用 HTTP 请求。这里的想法是将它放在后台请求中,让它在 100 多秒内完成它的工作,然后以某种方式将 PDF 返回给最终用户(例如自动下载)。我决定去做,并让我的代码工作到我有 Redis 服务器(Sidekiq 需要)、Sidekiq 服务器和通常的 rails 服务器都同时运行以允许我从加载和渲染 PDF Sidekiq 工作人员而不是控制器。

我的问题是“渲染”方法在工人中不可用!我试图通过使用直接从源访问它 av = ActionView::Base.new() 然后 av.render #pdf code here

但在我的 Sidekiq 控制台中出现以下错误:

“警告:NameError:未初始化的常量 PDFWorker::ActionView”

我的控制器中的代码:

# /app/controllers/recentgrad_controller.rb 
require 'sidekiq'
require "redis"
class RecentgradController < ApplicationController
  def report
    # things that prepare the name of the pdf, etc. go here

    PDFWorker.perform_async(pdf_name, pdf_year)
    redirect_to emphs_path
  end
end

我的工人中的代码:

# /app/workers/pdf_worker.rb
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 PDFWorker
  include Sidekiq::Worker
  sidekiq_options retry: false
  def perform(pdf_name, pdf_year)
    # create an instance of ActionView, so we can use the render method outside of a controller
    av = ActionView::Base.new() # THIS is where the error comes from
    av.view_paths = ActionController::Base.view_paths
    av.class_eval do
      include ActionController::UrlWriter
      include ApplicationHelper
    end

    av.render pdf: "mypdf", 
      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


我在运行生成 PDF 的程序部分时遇到的错误:

WARN: NameError: uninitialized constant PDFWorker::ActionView

【问题讨论】:

  • av = ::ActionView::Base.new() 与双冒号一起使用,这样它就不会在PDFWorker 命名空间中搜索类。
  • @DennyMueller 感谢您的回复!我进行了更改,重置了我的 sidekiq 服务器以确保更改生效,但仍然收到此消息:2019-05-23T15:55:45.665Z 5244 TID-5vcdw WARN: NameError: uninitialized constant ActionView 2019-05-23T15: 55:45.665Z 5244 TID-5vcdw WARN: /home/ec2-user/environment/gradSurvey/app/workers/pdf_worker.rb:16:in `perform' pdf_worker.rb 中的 16 是 av = ::ActionView:: Base.new() 知道为什么它还会这么说吗?
  • @DennyMueller 知道为什么双冒号不起作用吗?我也在::ApplicationController 上尝试过,但没有成功。
  • 不知道,您是否尝试过要求缺少的导轨部分? require 'action_view'

标签: amazon-web-services heroku ruby-on-rails-5 sidekiq wicked-pdf


【解决方案1】:

事实证明,使用作业而不是工作人员要容易得多,因为作业允许您使用从 ApplicationController 继承的渲染函数

这里真正的赢家是@unixmonkey,他是 wicked pdf for rails 的创建者,他为我设置了一个工作应用程序。你可以在这里看到问题:https://github.com/mileszs/wicked_pdf/issues/835

可以在此处查看真正改变其工作方式的提交: https://github.com/unixmonkey/generate_pdf_async_example/commit/85e8dcd98fe6580a8692842fcc5316b931ce4eea

享受吧!

【讨论】:

    【解决方案2】:

    Sidekiq 作业不是控制器操作,它没有参数、查询字符串、请求标头、cookie 等隐式状态。

    您可能会发现直接渲染模板更简单。我不确定您使用什么将 HTML 输出转换为 PDF,我在系统中使用 wkhtmltopdf。以下是我如何呈现一个简单的 HTML ERB 模板并将其转换为 PDF,以便我可以将其通过电子邮件发送给客户。

        require 'erb'
    
        localvar = "this is visible to the template"
        content = ERB.new(File.read("some_template.html.erb")).result(binding)
    
        tmpf = Tempfile.new(['sometemplate', '.html'])
        tmpf.write(content)
        tmpf.close
    
        result = `wkhtmltopdf #{tmpf.path} #{tmpf.path}.pdf 2>&1`
        raise result if $?.exitstatus != 0
    

    【讨论】:

    • 感谢@MikePerham 的回复我确实看到直接渲染模板更简单,但是时间太长了。我使用的 html.erb 文件多次查询 db 以获取页面上的正确信息,因此渲染功能只需大约 105 秒即可完成。我也在使用 wicked pdf,但是 Heroku HTTP 请求 30 秒超时阻止我按原样呈现它。你知道我怎么能解决这个问题吗?还是让它渲染得更快?是否可以在其他地方完成对 HTML 的所有查询,然后从完成的 html 中呈现 pdf?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-09
    • 2011-12-02
    • 2021-08-25
    • 2012-10-27
    • 2020-01-15
    • 2014-04-14
    • 1970-01-01
    相关资源
    最近更新 更多