【问题标题】:Using send_file to download a file from Amazon S3?使用 send_file 从 Amazon S3 下载文件?
【发布时间】:2012-08-29 23:14:20
【问题描述】:

我的应用中有一个下载链接,用户应该可以从中下载存储在 s3 上的文件。这些文件将在类似于

的网址上公开访问
https://s3.amazonaws.com/:bucket_name/:path/:to/:file.png

下载链接在我的控制器中触发了一个动作:

class AttachmentsController < ApplicationController
  def show
    @attachment = Attachment.find(params[:id])
    send_file(@attachment.file.url, disposition: 'attachment')
  end
end

但是当我尝试下载文件时出现以下错误:

ActionController::MissingFile in AttachmentsController#show

Cannot read file https://s3.amazonaws.com/:bucket_name/:path/:to/:file.png
Rails.root: /Users/user/dev/rails/print

Application Trace | Framework Trace | Full Trace
app/controllers/attachments_controller.rb:9:in `show'

该文件确实存在,并且可以通过错误消息中的 url 公开访问。

如何允许用户下载 S3 文件?

【问题讨论】:

    标签: ruby-on-rails ruby ruby-on-rails-3 amazon-s3 ruby-on-rails-3.2


    【解决方案1】:

    您也可以使用send_data

    我喜欢这个选项,因为你有更好的控制。您没有将用户发送到 s3,这可能会让某些用户感到困惑。

    我只想在AttachmentsController添加一个下载方法

    def download
      data = open("https://s3.amazonaws.com/PATTH TO YOUR FILE") 
      send_data data.read, filename: "NAME YOU WANT.pdf", type: "application/pdf", disposition: 'inline', stream: 'true', buffer_size: '4096' 
    end 
    

    并添加路线

    get "attachments/download"
    

    【讨论】:

    • 那么在这个解决方案中open方法不会先下载整个文件?并且 send_data 可以将文件从亚马逊流式传输给用户,而用户却不知道真正的 s3 文件路径?
    • 对于大文件,这可能会让等待服务器先下载文件然后将其流式传输给用户的用户感到困惑。此外,它需要的时间是用户直接从 S3 下载的两倍。
    • 使用carrierwave+S3文件,我让它像这样工作:article = Article.find params[:id]file_data = open(article.file.url)send_data file_data.read, filename: article.filename, type: article.file.content_type, disposition: 'attachment'
    • 值得指出,除了@JoshPinter 观察到它变慢(因为数据通过中介而不是直接传输)之外,它还会给您的服务器带来额外的负载并阻塞,尽管您可以卸载它到后台任务。直接从 S3 下载文件效率更高,而且 S3 有不错的错误页面 - 但 your AWS credentials will be visible when accessing private files.
    【解决方案2】:

    为用户保持简单

    我认为处理此问题的最佳方法是使用即将到期的 S3 网址。其他方法存在以下问题:

    • 文件先下载到服务器,再下载到用户。
    • 使用 send_data 不会产生预期的“浏览器下载”。
    • 绑定 Ruby 进程。
    • 需要额外的download 控制器操作。

    我的实现如下所示:

    在你的attachment.rb

    def download_url
      S3 = AWS::S3.new.buckets[ 'bucket_name' ] # This can be done elsewhere as well,
                                                # e.g config/environments/development.rb
      url_options = { 
        expires_in:                   60.minutes, 
        use_ssl:                      true, 
        response_content_disposition: "attachment; filename=\"#{attachment_file_name}\""
      }
    
      S3.objects[ self.path ].url_for( :read, url_options ).to_s
    end
    

    在你看来

    <%= link_to 'Download Avicii by Avicii', attachment.download_url %>
    

    就是这样。


    如果您出于某种原因仍想保留您的 download 操作,那么只需使用此操作:

    在你的attachments_controller.rb

    def download
      redirect_to @attachment.download_url
    end
    

    感谢guilleva 的指导。

    【讨论】:

    • 这会下载整个存储桶吗?我有一个类似的方法,我根据它们的密钥下载各个存储桶对象。
    • @BigRon 不,只是存储桶in 中的单个对象。但是再看看我的代码 sn-ps,我想我已经切掉了一个重要的部分!感谢您指出这一点!
    • @BigRon 现在看看。添加了您使用 S3 存储桶实际获取对象的部分。
    • 很好的修正,看起来不错。我将不得不尝试使用self.path。看起来比我现在的方法更简单的用法
    • @BigRon 让我知道self.path 是否适合您,因为我们实际上有一个可以去掉前导斜线的检查,但我不确定这是否在所有情况下都是必要的,或者我们是否需要在我们的案例中有一些特别之处:self.path.chr == '/' ? self.path[1..-1] : self.path )
    【解决方案3】:

    为了从您的网络服务器发送文件,

    • 你需要从 S3 下载它(见@nzajt's answer)或

    • 你可以redirect_to @attachment.file.expiring_url(10)

    【讨论】:

    • 如何在 S3 上使用非公共文件?
    • @MehulKar 在这种情况下你需要使用@attachment.file.expiring_url
    • 请注意,当访问 S3 上的私有文件时,如 here 所示,S3 URL 将包含您的秘密 AWS 凭证,以便浏览器可以发出经过身份验证的请求。如果下载有效并且用户流程保留在您的页面上,这并不明显,但是当它不起作用(例如文件不存在)时,S3 上的错误页面将在 URL 上显示凭据.这是一个相当大的安全风险。
    • 重定向到 url 不会自动下载文件,它只是将您带到它的浏览器视图,您可以在其中然后下载它
    • 我知道这篇文章已经过时了,但是因为这条评论被多次引用:no,过期的网址不会泄露你的凭据。它们显示您的访问 id 不是秘密,以及用于身份验证的预签名 hash。如果预签名的网址泄露了您的凭据,人们可以在网址过期时一遍又一遍地重新创建网址
    【解决方案4】:

    我刚刚将我的 public/system 文件夹迁移到了 Amazon S3。上述解决方案有帮助,但我的应用程序接受不同类型的文档。因此,如果您需要相同的行为,这对我有帮助:

    @document = DriveDocument.where(id: params[:id])
    if @document.present?
      @document.track_downloads(current_user) if current_user
      data = open(@document.attachment.expiring_url)
      send_data data.read, filename: @document.attachment_file_name, type: @document.attachment_content_type, disposition: 'attachment'
    end
    

    文件保存在DriveDocument 对象的attachment 字段中。 我希望这会有所帮助。

    【讨论】:

      【解决方案5】:

      以下内容最终对我来说效果很好。从 S3 对象中获取原始数据,然后使用send_data 将其传递给浏览器。

      使用aws-sdk 此处的 gem 文档 http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html

      完整的控制器方法

      def download
        AWS.config({
          access_key_id: "SECRET_KEY",
          secret_access_key: "SECRET_ACCESS_KEY"
        })
      
        send_data( 
          AWS::S3.new.buckets["S3_BUCKET"].objects["FILENAME"].read, {
            filename: "NAME_YOUR_FILE.pdf", 
            type: "application/pdf", 
            disposition: 'attachment', 
            stream: 'true', 
            buffer_size: '4096'
          }
        )
      end
      

      【讨论】:

      • 这必须先将文件下载到服务器,然后再发送给用户,对吗?
      • 正确,可能不是一个选项,具体取决于文件的大小。就我而言,它们是小型 PDF,这是可以接受的。
      【解决方案6】:

      我如何允许用户下载 S3 文件?

      如果您能够在将文件上传到 S3 之前在文件上设置一些元数据,而不是在用户稍后想要下载文件时尝试修补它,那么这个解决方案要简单得多:

      https://stackoverflow.com/a/24297799/763231

      如果您使用的是fog,那么您可以执行以下操作:

      has_attached_file :report,
        fog_file: lambda { |attachment|
          {
            content_type: 'text/csv',
            content_disposition: "attachment; filename=#{attachment.original_filename}",
          }
        }
      

      如果您使用 Amazon S3 作为您的存储提供商,那么 这样应该可以工作:

      has_attached_file :report
        s3_headers: lambda { |attachment|
          { 
            'Content-Type' => 'text/csv',
            'Content-Disposition' => "attachment; filename=#{attachment.original_filename}",
          }
        }
      

      【讨论】:

        【解决方案7】:

        def 下载_pdf @post= @post.avatar.service_url

        send_data(
        
            "#{Rails.root}/public/#{@post}",
            filename: "#{@post}",
            type: "image/*",
            disposition: 'inline', stream: 'true', buffer_size: '4096'
        )
        

        结束

        【讨论】:

        • 如果可能,请努力提供额外的解释,而不仅仅是代码。此类答案往往更有用,因为它们可以帮助社区成员,尤其是新开发人员更好地理解解决方案的推理,并有助于避免需要解决后续问题。
        • 如果可能,请努力提供额外的解释,而不仅仅是代码。此类答案往往更有用,因为它们可以帮助社区成员,尤其是新开发人员更好地理解解决方案的推理,并有助于避免需要解决后续问题。
        猜你喜欢
        • 2012-10-10
        • 2013-05-08
        • 1970-01-01
        • 2017-06-18
        • 1970-01-01
        • 1970-01-01
        • 2020-05-29
        • 2014-11-28
        • 1970-01-01
        相关资源
        最近更新 更多