我如何使用 Sidekiq 将大型 PDF 作为后台进程呈现,以避免 Heroku 的 30 秒 HTTP 请求超时?

How can I use Sidekiq to render large PDFs as a background process to avoid Heroku's 30 second timeout for HTTP requests?

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

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

我在另一个 post 中询问了这个问题,并得到了反馈,尝试使用 Sidekiq 之类的东西和 rails 来处理大量进程,而不是尝试使用 HTTP 请求。这里的想法是将它放在后台请求中,让它在 100 多秒内完成它的工作,然后 return 以某种方式将 PDF 发送给最终用户(例如让它自动下载)。我决定去做,并让我的代码工作到我拥有 Redis 服务器(Sidekiq 需要)、Sidekiq 服务器和通常的 rails 服务器所有 运行 齐心协力的地步允许我从 Sidekiq worker 而不是控制器加载和渲染 PDF。

我的问题是 'render' 方法在 workers 中不可用!我试图通过使用直接从源访问它 av = ActionView::Base.new() 接着 av.render #pdf code here

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

"WARN: NameError: uninitialized constant 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

我的worker中的代码:

# /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

Sidekiq 作业不是控制器操作,它没有参数、查询字符串、请求 headers、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

事实证明,使用作业而不是工人要容易得多,因为作业允许您使用继承自 ApplicationController

的渲染函数

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

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

尽情享受吧!