Rails:允许用户下载存储在S3上的文件,而不显示实际的S3 URL

30
我有一个托管在Heroku上的Rails应用程序。应用程序生成并存储PDF文件在Amazon S3上。用户可以下载这些文件以在其浏览器中查看或保存到他们的电脑上。
我的问题是,虽然通过S3 URL(例如“https://s3.amazonaws.com/my-bucket/F4D8CESSDF.pdf”)下载这些文件是可行的,但显然这不是一个好方法。向用户公开关于后端的如此多信息是不可取的,更不用说可能出现的安全问题了。
是否可能让我的应用程序在控制器中从S3检索文件数据,然后为用户创建下载流,以便Amazon URL不会暴露?

你显然可以下载它,然后将其流回用户,但速度不会特别快。 - Frederick Cheung
3个回答

59

您可以创建私有的S3对象,并使用url_for方法(aws-s3 gem)生成临时公共链接。这样可以避免将文件通过应用服务器进行流式传输,从而使应用更加可扩展。此外,它还允许放置基于会话的授权(例如,在您的应用程序中使用devise),跟踪下载事件等。

为此,将指向托管在S3上的文件的直接链接更改为指向控制器/操作的链接,该控制器/操作创建临时URL并重定向到它。像这样:

class HostedFilesController < ApplicationController

  def show
    s3_name = params[:id] # sanitize name here, restrict access to only some paths, etc
    AWS::S3::Base.establish_connection!( ... )
    url = AWS::S3::S3Object.url_for(s3_name, YOUR_BUCKET, :expires_in => 2.minutes)
    redirect_to url
  end

end

通常通过DNS别名来隐藏下载链接中的Amazon域名。您需要创建 CNAME 记录别名,将您的子域名,例如downloads.mydomain,指向 s3.amazonaws.com。然后您可以在AWS::S3::Base.establish_connection!(:server => "downloads.mydomain", ...)中指定:server选项,S3 gem 将使用该选项生成链接。


1
Serge,我试了你的方法。它有效,但是URL看起来像这样:http://s3.amazonaws.com/my-bucket/F4D8CED0.pdf?AWSAccessKeyId=my-actual-key&Expires=1346904098&Signature=Wo7wYH6Ek%2FBot3wd2xYbGt4ybSU%3D。 理想情况下,我希望没有提到亚马逊,而是像“http://mydomain.com/download/file.pdf”这样的东西。 这可能吗? - futureshocked
1
你可以通过CNAME来实现这个功能,但是它会更像是something.mydomain.com/path/file.pdf。我已经更新了答案以反映这一点。 - Serge Balyuk
您应该将content-disposition设置为附件,以便强制浏览器下载它,而不是尝试在窗口中打开它。 - Joshua Pinter

11
是的,这是可能的 - 只需使用Rails获取远程文件,然后将其暂时存储在您的服务器上或直接从缓冲区发送。当然,这样做的问题在于您需要先获取文件,然后才能将其提供给用户。有关讨论,请参见此线程,他们的解决方案类似于这样:
#environment.rb
require 'open-uri'

#controller
def index
  data = open(params[:file])
  send_data data, :filename => params[:name], ...
end

这个问题也有些相关。


这个问题最终变得很简单,谢谢。这是我的实际实现:def download file_name = params["file_id"] + ".pdf" data = open("https://s3.amazonaws.com/#{ENV['S3_BUCKET_NAME']}/#{file_name}") send_data data.read, :filename => file_name, :type => "application/pdf", :disposition => 'attachment', :stream => 'true', :buffer_size => '4096' end我已经接受了您的解决方案作为解决此问题的最简单方法。 - futureshocked
1
Serge的方法也不错,它可以让Rails应用程序免于处理下载,只需要再做一些工作就可以了。 - futureshocked
1
只是让其他人知道,我发现这个有两个问题:1)它会先下载到服务器,然后再下载到用户端,这可能需要一些时间;2)用户的体验并不是他们所期望的。与其在浏览器中下载,看起来像是正在加载某些内容。请查看https://dev59.com/-Wct5IYBdhLWcg3wL6mi#23161355以获取更多讨论。 - Joshua Pinter

-1
首先,您需要在您的域名中创建一个CNAME,例如在这里here进行解释。
其次,您需要创建一个与您在CNAME中放置的相同名称的存储桶。
最后,您需要在config/initializers/carrierwave.rb中添加这些配置。
CarrierWave.configure do |config|
    ...  
    config.asset_host         = 'http://bucket_name.your_domain.com'
    config.fog_directory      = 'bucket_name.your_domain.com'
    ...
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接