Ruby on rails 使用send_file从AmazonS3下载文件?

Ruby on rails 使用send_file从AmazonS3下载文件?,ruby-on-rails,ruby,ruby-on-rails-3,amazon-s3,ruby-on-rails-3.2,Ruby On Rails,Ruby,Ruby On Rails 3,Amazon S3,Ruby On Rails 3.2,我的应用程序中有一个下载链接,用户可以从中下载存储在s3上的文件。这些文件将在URL上公开访问,URL类似 https://s3.amazonaws.com/:bucket_name/:path/:to/:file.png 下载链接点击我的控制器中的操作: class AttachmentsController < ApplicationController def show @attachment = Attachment.find(params[:id]) sen

我的应用程序中有一个下载链接,用户可以从中下载存储在s3上的文件。这些文件将在URL上公开访问,URL类似

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
该文件确实存在,并且可以在错误消息中的url处公开访问


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

为了从web服务器发送文件

  • 您需要从S3(请参阅)或

  • 您可以
    重定向到@attachment.file.expiring\u url(10)

    • 您也可以使用

      我喜欢这个选项,因为你有更好的控制。您没有将用户发送到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"
      

      以下是最终对我有效的方法。从S3对象获取原始数据,然后使用
      send_data
      将其传递到浏览器

      使用此处找到的aws sdkgem文档

      完全控制器方法

      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
      
      为用户保持简单 我认为最好的处理方法是使用一个即将过期的S3URL。其他方法存在以下问题:

      • 文件首先下载到服务器,然后再下载到用户
      • 使用
        send_data
        不会产生预期的“浏览器下载”
      • 绑定Ruby进程
      • 需要额外的
        下载
        控制器操作
      我的实现如下所示:

      在您的
      附件.rb
      在你看来
      感谢他的指导。

      我刚刚将我的
      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
      
      文件保存在
      驱动文档
      对象的
      附件
      字段中。 我希望这能有所帮助。

      def下载\u pdf @post=@post.avatar.service\u url

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

      如何允许用户下载S3文件

      如果您能够在将文件上载到S3之前在文件上设置一些元数据,而不是在用户稍后需要下载时尝试对其进行修补,则此解决方案简单得多:

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

      has_attached_file :report,
        fog_file: lambda { |attachment|
          {
            content_type: 'text/csv',
            content_disposition: "attachment; filename=#{attachment.original_filename}",
          }
        }
      
      如果您使用AmazonS3作为存储提供商,那么 这样应该可以:

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


      如何将其用于S3上的非公共文件?@MehulKar在这种情况下,您需要使用
      @attachment.file.expiring_url
      注意:如图所示,访问S3上的私有文件时,S3 url将包含您的秘密AWS凭据,以便浏览器可以发出经过身份验证的请求。如果下载有效并且用户流保持在您的页面上,这并不明显,但是当下载无效时(例如文件不存在),S3上的错误页面将在URL上显示凭据。这是一个相当大的安全风险。重定向到url不会自动下载文件,它只会将您带到它的浏览器视图,然后您可以在那里下载它。因此在此解决方案中,
      open
      方法不会先下载整个文件?而send_data可以将文件从amazon流式传输给用户,而用户根本不知道真正的s3文件路径?肯定是这样,但是对于大文件,似乎不需要
      &
      缓冲区大小
      选项,对于等待服务器先下载文件然后将其流式传输给用户的用户来说,这可能会造成混淆。另外,它需要的时间是用户直接从S3下载的时间的两倍。使用carrierwave+S3文件,我使其工作如下:
      article=article.find params[:id]
      file\u data=open(article.file.url)
      发送数据文件\u data.read,文件名:article.filename,type:article.file.content\u type,处置:'attachment'
      值得指出的是,除了@JoshPinter观察到它的速度较慢(因为数据通过中间层而不是直接传输),它还会给服务器带来额外的负载并阻塞,尽管您可以将其卸载到后台任务中。直接从S3下载文件效率更高,加上S3有相当多的错误页,但这必须在发送给用户之前将文件下载到服务器,对吗?对,根据文件的大小,可能不是一个选项。在我的例子中,它们是小PDF,这是可以接受的。这会下载整个存储桶吗?我有一个类似的方法,根据各个bucket对象的键下载它们。@BigRon不,只下载bucket中的单个对象。但再看看我的代码片段,我想我已经切掉了一个重要的部分!谢谢你指出这一点@比格伦,现在看看。添加了使用S3桶实际获取对象的部分。很好的更正,看起来不错。我必须尝试您使用
      self.path
      。它看起来比我现在使用的更简单method@BigRon请告诉我
      self.path
      是否适用于您,因为我们实际上有一个删除前导斜杠的检查,但我不确定这是否在所有情况下都是必要的,或者我们是否有一些特殊的情况:
      self.path.chr=='/'?self.path[1..-1]:self.path)
      如果可能,请尽力提供其他解释,而不仅仅是代码。这些答案往往更有用,因为它们有助于社区成员,特别是新开发人员更好地理解解决方案的理由,并有助于避免需要解决后续问题。如果可能,请努力提供额外的解释,而不仅仅是代码。这些答案往往更有用,因为它们有助于社区成员,特别是新开发人员更好地理解其中的原因
      @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
      
      send_data(
      
          "#{Rails.root}/public/#{@post}",
          filename: "#{@post}",
          type: "image/*",
          disposition: 'inline', stream: 'true', buffer_size: '4096'
      )
      
      has_attached_file :report,
        fog_file: lambda { |attachment|
          {
            content_type: 'text/csv',
            content_disposition: "attachment; filename=#{attachment.original_filename}",
          }
        }
      
      has_attached_file :report
        s3_headers: lambda { |attachment|
          { 
            'Content-Type' => 'text/csv',
            'Content-Disposition' => "attachment; filename=#{attachment.original_filename}",
          }
        }