【问题标题】:rails - x-sendfile + temporary filesrails - x-sendfile + 临时文件
【发布时间】:2011-05-18 10:35:25
【问题描述】:

前段时间我写了a question 关于在rails 应用程序中使用临时文件的问题。在特殊情况下,我决定使用tempfile

如果我还想使用 x-sendfile 指令 (as a parameter in Rails 2, or as a configuration option in Rails 3) 以便文件发送由我的 Web 服务器直接处理,而不是我的 Rails 应用程序,这会导致问题。

所以我想过做这样的事情:

require 'tempfile'

def foo()
  # creates a temporary file in tmp/
  Tempfile.open('prefix', "#{Rails.root}/tmp") do |f|
    f.print('a temp message')
    f.flush
    send_file(f.path, :x_sendfile => true) # send_file f.path in rails 3
  end
end

此设置有一个问题:文件在发送前被删除!

一方面,tempfile 将在 Tempfile.open 块结束后立即删除文件。另一方面,x-sendfile 使 send_file 调用异步 - 它返回非常快,因此服务器几乎没有时间发送文件。

我现在最好的解决方案是使用非临时文件(文件而不是临时文件),然后是一个定期擦除临时文件夹的 cron 任务。这有点不雅,因为:

  • 我必须使用自己的临时文件命名方案
  • 文件在 tmp 文件夹中的停留时间超过了所需时间。

有更好的设置吗?或者,异步send_file上是否至少有一个“成功”回调,所以我可以在完成后擦除f?

非常感谢。

【问题讨论】:

    标签: ruby-on-rails ruby temporary-files x-sendfile


    【解决方案1】:

    鉴于 Rails3 在可用时使用 x-sendfile,并且无法停用它,因此您不能将 send_file 与诸如 TempFile 之类的库一起使用。最好的选择是我在问题中提到的那个:使用常规文件,并有一个定期删除旧临时文件的 cron 任务。

    编辑:现在使用 maid gem 可以更轻松地删除未使用的文件:

    https://github.com/benjaminoakes/maid

    【讨论】:

    • 感谢提到女仆。我很感激。 :)
    • 感谢您创建它! :)
    【解决方案2】:

    不要将 send_file 放在块中。

    f = Tempfile.new('prefix', "#{Rails.root}/tmp")
    f.print('a temp message')
    f.close
    send_file(f.path, :x-sendfile => true)
    

    然后使用另一个脚本清理临时文件

    【讨论】:

    • 恐怕这无论如何都行不通。 Tempfile “足够聪明”,可以检测到定义 f 的范围已经结束,并且无论如何都会删除该文件。该块只是让它更明确一点。
    • 别猜了,试试看: def create_tmp_file f = Tempfile.new("foo", ".") f.write("a" * 1024) f.close f.path end path = create_tmp_file sleep 60 puts File.exists?(path) puts File.read(path)
    • 我试过了。 f 的范围在您的示例中仍然有效。退出控制台,文件将消失。当控制器操作返回时也会发生同样的情况。
    • f 的作用域已经结束,但是 gc 取消了文件的链接。请参阅 Tempfile#callback。使用普通文件即可。
    • 我知道。这就是我现在正在做的事情。我在问是否有更好的方法。
    【解决方案3】:

    file-temp gem 怎么样? https://github.com/djberg96/file-temp

    require 'file/temp'
    
    fh = File::Temp.new(false)
    fh.puts "world"
    fh.close # => Tempfile still on your filesystem
    

    就像 zzzhc 的回答一样,您需要在外部管理清理

    【讨论】:

    • 感谢您的回答。如果我必须自己管理文件,那么我想我会坚持使用常规 File 实例。库提供的另一件事(根据主机计算默认临时文件夹)对我来说并不有趣,因为我已经有了“#{Rails.root}/tmp”
    【解决方案4】:

    您可以取消定义 Tempfile 实例的终结器,这样当实例被销毁时您的文件永远不会被删除,然后让 chron 任务处理它。

    require 'tempfile'
    
    def foo()
      # creates a temporary file in tmp/
      Tempfile.open('prefix', "#{Rails.root}/tmp") do |f|
        f.print('a temp message')
        f.flush
        ObjectSpace.undefine_finalizer(f) # 'disables' deletion when GC'ed
        send_file(f.path, :x_sendfile => true) # send_file f.path in rails 3
     end
    end
    

    【讨论】: