使用send_file从Amazon S3下载文件?

时间:2023-02-06 00:19:30

I have a download link in my app from which users should be able to download files which are stored on s3. These files will be publicly accessible on urls which look something like

我的应用程序中有一个下载链接,用户可以从其中下载存储在s3上的文件。这些文件可以在url上公开访问,url看起来像这样

https://s3.amazonaws.com/:bucket_name/:path/:to/:file.png

The download link hits an action in my controller:

下载链接在我的控制器中点击一个动作:

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

But I get the following error when I try to download a file:

但是当我尝试下载一个文件时,我有以下错误:

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'

The file definitely exists and is publicly accessible at the url in the error message.

该文件肯定存在,并且可以在错误消息的url上公开访问。

How do I allow users to download S3 files?

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

5 个解决方案

#1


28  

In order to send a file from your web server,

为了从web服务器发送文件,

  • you need to download it from S3 (see @nzajt's answer) or

    您需要从S3(参见@nzajt的答案)或

  • you can redirect_to @attachment.file.expiring_url(10)

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

#2


71  

You can also use send_data.

您还可以使用send_data。

I like this option because you have better control. You are not sending users to s3, which might be confusing to some users.

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

I would just add a download method to the AttachmentsController

我只需要向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 

and add the route

并添加路线

get "attachments/download"

#3


27  

Keep Things Simple For The User

I think the best way to handle this is using an expiring S3 url. The other methods have the following issues:

我认为最好的处理方法是使用过期的S3 url。其他方法有以下问题:

  • The file downloads to the server first and then to the user.
  • 文件先下载到服务器,然后再下载到用户。
  • Using send_data doesn't produce the expected "browser download".
  • 使用send_data不会产生预期的“浏览器下载”。
  • Ties up the Ruby process.
  • 绑定Ruby进程。
  • Requires an additional download controller action.
  • 需要额外的下载控制器动作。

My implementation looks like this:

我的实现是这样的:

In your 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

In your views

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

That's it.

就是这样。


If you still wanted to keep your download action for some reason then just use this:

如果出于某种原因你还想继续你的下载行为,那就用这个:

In your attachments_controller.rb

在你attachments_controller.rb

def download
  redirect_to @attachment.download_url
end

Thanks to guilleva for his guidance.

感谢吉尔瓦的指导。

#4


3  

The following is what ended up working well for me. Getting the raw data from the S3 object and then using send_data to pass that on to the browser.

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

Using the aws-sdk gem documentation found here http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html

使用这里找到的aws-sdk gem文档http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html

full controller method

完整的控制器方法

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

#5


3  

I have just migrated my public/system folder to Amazon S3. Solutions above help but my app accepts different kinds of documents. So if you need the same behavior, this helps for me:

我刚刚将公共/系统文件夹迁移到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

The file is being saved in the attachment field of DriveDocument object. I hope this helps.

文件被保存在DriveDocument对象的附件字段中。我希望这可以帮助。

#1


28  

In order to send a file from your web server,

为了从web服务器发送文件,

  • you need to download it from S3 (see @nzajt's answer) or

    您需要从S3(参见@nzajt的答案)或

  • you can redirect_to @attachment.file.expiring_url(10)

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

#2


71  

You can also use send_data.

您还可以使用send_data。

I like this option because you have better control. You are not sending users to s3, which might be confusing to some users.

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

I would just add a download method to the AttachmentsController

我只需要向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 

and add the route

并添加路线

get "attachments/download"

#3


27  

Keep Things Simple For The User

I think the best way to handle this is using an expiring S3 url. The other methods have the following issues:

我认为最好的处理方法是使用过期的S3 url。其他方法有以下问题:

  • The file downloads to the server first and then to the user.
  • 文件先下载到服务器,然后再下载到用户。
  • Using send_data doesn't produce the expected "browser download".
  • 使用send_data不会产生预期的“浏览器下载”。
  • Ties up the Ruby process.
  • 绑定Ruby进程。
  • Requires an additional download controller action.
  • 需要额外的下载控制器动作。

My implementation looks like this:

我的实现是这样的:

In your 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

In your views

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

That's it.

就是这样。


If you still wanted to keep your download action for some reason then just use this:

如果出于某种原因你还想继续你的下载行为,那就用这个:

In your attachments_controller.rb

在你attachments_controller.rb

def download
  redirect_to @attachment.download_url
end

Thanks to guilleva for his guidance.

感谢吉尔瓦的指导。

#4


3  

The following is what ended up working well for me. Getting the raw data from the S3 object and then using send_data to pass that on to the browser.

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

Using the aws-sdk gem documentation found here http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html

使用这里找到的aws-sdk gem文档http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html

full controller method

完整的控制器方法

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

#5


3  

I have just migrated my public/system folder to Amazon S3. Solutions above help but my app accepts different kinds of documents. So if you need the same behavior, this helps for me:

我刚刚将公共/系统文件夹迁移到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

The file is being saved in the attachment field of DriveDocument object. I hope this helps.

文件被保存在DriveDocument对象的附件字段中。我希望这可以帮助。