Nếu file trên S3 được public thì chỉ cần trả về url của file cho user, khi user click vào url đó trên browser thì họ sẽ download được.

Còn nếu file trên S3 là private và ta muốn download file thông qua API thì làm như sau:

  • Download S3 file rồi lưu về file tạm(temporary file) trên server.
  • Gửi nội dung file tạm về cho người dùng
  • Xóa file tạm trên server(có thể xóa hoặc không nhưng nên xóa đi)

Khi không cần xóa file tạm thì bạn chỉ cần làm như sau (ở đây mình dùng gem aws-sdk-resources, nếu bạn dùng gem khác thì gọi với API tương ứng):

def download
  s3 = Aws::S3::Resource.new(region: ENV['AWS_REGION'])
  obj = s3.bucket(ENV['BUCKET_NAME']).object("file_path_in_S3")
  tempFile = Tempfile.new('test')
  obj.get(response_target: tempFile.path)
  send_file(tempFile.read, filename: "file_name")
end

 

Khi gọi send_file Rails server sẽ gửi request tới HTTP server(Apache hoặc Nginx), HTTP server khi nhận được request send_file sẽ gửi file cho client. Vì thế, nếu ta xóa temp file ngay sau send_file sẽ phát sinh lỗi do Rails không biết được là HTTP server có đang sử dụng file tạm đó hay không.

Đoạn code sau sẽ trả về file không đúng cho client:

def download
  s3 = Aws::S3::Resource.new(region: ENV['AWS_REGION'])
  obj = s3.bucket(ENV['BUCKET_NAME']).object("file_path_in_S3")
  tempFile = Tempfile.new('test')
  obj.get(response_target: tempFile.path)
  send_file(tempFile.read, filename: "file_name")
  tempFile.unlink  # wrong here
end

 

Để xóa được file tạm thì thay vì dùng send_file ta dùng send_data như sau:

def download
  s3 = Aws::S3::Resource.new(region: ENV['AWS_REGION'])
  obj = s3.bucket(ENV['BUCKET_NAME']).object("file_path_in_S3")
  tempFile = Tempfile.new('test')
  obj.get(response_target: tempFile.path)
  File.open(tempFile.path, 'r') do |f|
    send_data(f.read, filename: "file_name")
  end
ensure
  if tempFile.present?
    tempFile.close
    tempFile.unlink
  end
end

 

Link tham khảo:

https://stackoverflow.com/questions/5535981/what-is-the-difference-between-send-data-and-send-file-in-ruby-on-rails