Tệp zip S3 trước khi tải xuống

Trở lại năm 2012, chúng tôi đã thêm tùy chọn “Tải xuống nhiều tệp” cho Dự án làm việc theo nhóm. Tuy nhiên, tùy chọn này phụ thuộc vào sự hỗ trợ của trình duyệt và đổ tất cả các tệp vào thư mục "tải xuống" của trình duyệt mà không giữ cấu trúc thư mục của danh mục

Trong nhiều năm, chúng tôi đã cố gắng tìm thời gian để thêm tùy chọn tải xuống ZIP tốt hơn để tải xuống tất cả các tệp trong một gói trong khi vẫn duy trì cấu trúc thư mục của các danh mục đã xác định

Ở đây, tôi phác thảo cách chúng tôi xây dựng một dây kéo tệp sang trọng chỉ trong một đêm nhờ sức mạnh của Go. Ngay cả khi bạn hiện không sử dụng Go (hay còn gọi là “Golang”, một ngôn ngữ của Google mà chúng tôi rất hâm mộ), cơ chế mà chúng tôi trình bày ở đây hoạt động với ngôn ngữ phía máy chủ mà bạn chọn và bạn chỉ cần chạy trình kéo tệp dưới dạng

Nóng nảy? . com/Làm việc theo nhóm/s3zipper

Giải pháp phát trực tuyến

Cách tiêu chuẩn để cung cấp bản sao lưu của các tệp S3 là tải tất cả các tệp xuống thư mục tạm thời, nén chúng và sau đó cung cấp tệp đã nén. Tuy nhiên, phương pháp đó bắt đầu chậm đối với người dùng, chiếm nhiều dung lượng tệp máy chủ và yêu cầu dọn dẹp. Đó chỉ là chậm, không thanh lịch và lộn xộn

Điều gì sẽ xảy ra nếu chúng ta có thể hấp các tệp cho người dùng trong khi nén chúng một cách nhanh chóng. Được gọi là 'Piping', chúng tôi sẽ không phải lưu trữ tệp, thực hiện dọn dẹp và khiến người dùng phải đợi quá trình tải xuống bắt đầu

Chà, đó chính xác là những gì chúng tôi đã làm, chỉ trong vài giờ nhờ sức mạnh của Go

Chỉ cần cho tôi xem Bleedin' Code

đủ của tôi ca ngợi. Nếu bạn đang đọc điều này và bạn cũng giống như tôi, bạn muốn thử mã làm việc. Nhưng trước tiên, hãy để tôi phác thảo ngắn gọn về cách thức hoạt động của quá trình tải xuống và bảo mật. -

  • Nền tảng chính của chúng tôi nhận yêu cầu API cho tệp zip với một số tệpId được thông qua. e. g. tải xuống/zip?fileIds=83748,379473,93894
  • Sau đó, nền tảng sẽ xác thực người dùng như bình thường và lấy thông tin chi tiết về các tệp từ cơ sở dữ liệu của chúng tôi
  • Sau đó, nó tạo một chuỗi tham chiếu tải xuống duy nhất và đặt một mảng có mô tả các tệp vào Redis với chuỗi tham chiếu làm khóa, e. g. “khóa kéo. gdi63783hdhA73”. Các mô tả tệp bao gồm tên tệp, đường dẫn thư mục và đường dẫn tệp s3. Phím được đặt thành thời gian chờ sau năm phút
  • Chúng tôi chỉ cần chuyển hướng người dùng đến tệp s3 zip đi dọc theo chuỗi tham chiếu. e. g. , dây kéo. tinh thần đồng đội. com?ref=gdi63783hdhA73

Bản thân zip tệp s3 không phải thực hiện bảo mật. Nếu có key thì vui lòng tiến hành. Nó chỉ nhận một yêu cầu với một chuỗi tham chiếu, yêu cầu Redis cung cấp các tệp tương ứng và bắt đầu kéo chúng từ S3 đồng thời nén các khối và gửi chúng đến máy khách. Nó chỉ là một cỗ máy đẹp ngu ngốc. Đây là mã

package mainimport (
"archive/zip"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"regexp"
"strconv"
"strings"
"time"
"net/http""github.com/AdRoll/goamz/aws"
"github.com/AdRoll/goamz/s3"
redigo "github.com/garyburd/redigo/redis"
)
type Configuration struct {
AccessKey string
SecretKey string
Bucket string
Region string
RedisServerAndPort string
Port int
}
var config = Configuration{}
var aws_bucket *s3.Bucket
var redisPool *redigo.Pool
func main() {configFile, _ := os.Open("conf.json")
decoder := json.NewDecoder(configFile)
err := decoder.Decode(&config)
if err != nil {
panic("Error reading conf")
}
initAwsBucket()
InitRedis()
fmt.Println("Running on port", config.Port)
http.HandleFunc("/", handler)
http.ListenAndServe(":" strconv.Itoa(config.Port), nil)
}
func initAwsBucket() {
now := time.Now()
var dur time.Duration = time.Hour * 1
expiration := now.Add(dur)
auth, err := aws.GetAuth(config.AccessKey, config.SecretKey, "", expiration) //"" = token which isn't needed
if err != nil {
panic(err)
}
aws_bucket = s3.New(auth, aws.GetRegion(config.Region)).Bucket(config.Bucket)
}
func InitRedis() {
redisPool = &redigo.Pool{
MaxIdle: 10,
IdleTimeout: 1 * time.Second,
Dial: func() (redigo.Conn, error) {
return redigo.Dial("tcp", config.RedisServerAndPort)
},
TestOnBorrow: func(c redigo.Conn, t time.Time) (err error) {
_, err = c.Do("PING")
if err != nil {
panic("Error connecting to redis")
}
return
},
}
}
// Remove all other unrecognised characters apart from
var makeSafeFileName = regexp.MustCompile(`[#<>:"/\|?*\\]`)
type RedisFile struct {
FileName string
Folder string
S3Path string
// Optional - we use are Teamwork.com but feel free to rmove
FileId int64
ProjectId int64
ProjectName string
}
func getFilesFromRedis(ref string) (files []*RedisFile, err error) {// Testing - enable to test. Remove later.
if 1 == 0 && ref == "test" {
files = append(files, &RedisFile{FileName: "test.zip", Folder: "", S3Path: "test/test.zip"}) // Edit and dplicate line to test
return
}
redis := redisPool.Get()
defer redis.Close()
// Get the value from Redis
result, err := redis.Do("GET", "zip:" ref)
if err != nil || result == nil {
err = errors.New("Reference not found")
return
}
// Decode the JSON
var resultByte []byte
var ok bool
if resultByte, ok = result.([]byte); !ok {
err = errors.New("Error reading from redis")
return
}
err = json.Unmarshal(resultByte, &files)
if err != nil {
err = errors.New("Error decoding files redis data")
}
return
}
func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Get "ref" URL params
refs, ok := r.URL.Query()["ref"]
if !ok || len(refs) < 1 { http.Error(w, "S3 File Zipper. Pass ?ref= to use.", 500) return } ref := refs[0] // Get "downloadas" URL params downloadas, ok := r.URL.Query()["downloadas"] if !ok && len(downloadas) > 0 {
downloadas[0] = makeSafeFileName.ReplaceAllString(downloadas[0], "")
if downloadas[0] == "" {
downloadas[0] = "download.zip"
}
} else {
downloadas = append(downloadas, "download.zip")
}
files, err := getFilesFromRedis(ref)
if err != nil {
http.Error(w, "Access Denied (Link has probably timed out)", 403)
log.Printf("Link timed out. %s\t%s", r.Method, r.RequestURI)
return
}
// Start processing the response
w.Header().Add("Content-Disposition", "attachment; filename=\"" downloadas[0] "\"")
w.Header().Add("Content-Type", "application/zip")
// Loop over files, add them to the
zipWriter := zip.NewWriter(w)
for _, file := range files {
// Build safe file file name
safeFileName := makeSafeFileName.ReplaceAllString(file.FileName, "")
if safeFileName == "" { // Unlikely but just in case
safeFileName = "file"
}
// Read file from S3, log any errors
rdr, err := aws_bucket.GetReader(file.S3Path)
if err != nil {
switch t := err.(type) {
case *s3.Error:
if t.StatusCode == 404 {
log.Printf("File not found. %s", file.S3Path)
}
default:
log.Printf("Error downloading \"%s\" - %s", file.S3Path, err.Error())
}
continue
}
// Build a good path for the file within the zip
zipPath := ""
// Prefix project Id and name, if any (remove if you don't need)
if file.ProjectId > 0 {
zipPath = strconv.FormatInt(file.ProjectId, 10) "."
// Build Safe Project Name
file.ProjectName = makeSafeFileName.ReplaceAllString(file.ProjectName, "")
if file.ProjectName == "" { // Unlikely but just in case
file.ProjectName = "Project"
}
zipPath = file.ProjectName "/"
}
// Prefix folder name, if any
if file.Folder != "" {
zipPath = file.Folder
if !strings.HasSuffix(zipPath, "/") {
zipPath = "/"
}
}
// Prefix file Id, if any
if file.FileId > 0 {
zipPath = strconv.FormatInt(file.FileId, 10) "."
}
zipPath = safeFileName
// We have to set a special flag so zip files recognize utf file names
// See http://stackoverflow.com/questions/30026083/creating-a-zip-archive-with-unicode-filenames-using-gos-archive-zip
h := &zip.FileHeader{Name: zipPath, Method: zip.Deflate, Flags: 0x800}
f, _ := zipWriter.CreateHeader(h)
io.Copy(f, rdr)
rdr.Close()
}
zipWriter.Close()log.Printf("%s\t%s\t%s", r.Method, r.RequestURI, time.Since(start))
}
View S3Zipper on GitHubIt's extremely fast, low memory, and can handle thousands of simultaneous requests. It's also secure (auth done elsewhere and keys timeout) and very simple.After years of wanting to get this feature done, it was just one long night's work thanks to the power of Go and some of its fantastic open source and internal libraries.You'll see some voodoo around line 211 - this was added to provide UTF character support for our many international customers.TestingIf you want to quickly test this:
  • Cài đặt Go, nếu bạn chưa có
  • Sao chép repo s3Zipper
  • Cung cấp tệp cấu hình (dựa trên mẫu. cấu hình)
  • Chạy “go s3zipper. go” và duyệt đến http. //máy chủ cục bộ. 8000/?ref=kiểm tra.

    (Nếu mới sử dụng, bạn cần chạy “go get” trước để nhận lib)

Các tệp của bạn sẽ được tải xuống dưới dạng tệp Zip ngay lập tức

chuyển sang sản xuất

Bây giờ, bạn chỉ cần chạy ứng dụng này trên máy chủ và yêu cầu ngôn ngữ phía máy chủ của bạn đặt các định nghĩa tệp vào bộ nhớ, sau đó chuyển hướng người dùng đến microservice

Thiết lập vi dịch vụ S3Zipper

  • Kích hoạt máy chủ EC2 Ubuntu mới (Tôi đã sử dụng [S3Type và hình ảnh Ubuntu])
  • Cài đặt đi. Không cài đặt Đi qua apt-get. Tại thời điểm viết nó là một phiên bản lỗi thời của go. Cài đặt đi từ nguồn — hướng dẫn
  • Tạo một người dùng mới để chạy dịch vụ dưới
  • Sao chép repo của chúng tôi và thanh toán với máy chủ
  • Tạo tập tin cấu hình của bạn
  • Kiểm tra tập lệnh với go run s3zipper. go (chạy “go get” trước để lấy thư viện)
  • Chạy như một dịch vụ bằng cách sử dụng tập lệnh khởi động bên dưới
Upstart ConfigCopy this upstart script to etc/init to run this as a service:

s3zipper.conf
description "S3 File Zipping Server"
author "[You]"
start on started mountall
stop on shutdown
respawn
respawn limit 99 5
script
export HOME="/home/USERX"
export GOPATH="/home/USERX/go"
export GOROOT="/home/USERX/.gvm/gos/go1.4.2"
export PATH=$PATH:$GOPATH/bin:$GOROOT/bin
ulimit -n 50000cd /full/path/to/s3zipper
exec setuidgid USERX go run s3zipper.go &gt;&gt; /var/log/s3zipper.log 2&gt;&amp;1
end script
Replace USERX with your new user, set the GOPATH and GOROOT correctly and fix up full/path/to. If this is new to you, see Upstart - Getting StartedServing up the Zip Download

Bạn sẽ cần thực hiện cuộc gọi phía máy chủ bằng ngôn ngữ bạn chọn sẽ

  • Xác thực người dùng (như bình thường)
  • Tạo mã tham chiếu ngẫu nhiên duy nhất
  • Đặt thông tin chi tiết về các tệp sẽ tải xuống vào Redis bằng khóa “zip. [ref]” (thời gian chờ 5 phút). Lưu ý rằng các tập tin phải ở định dạng.

    ``` [{“S3Path”. ”đường dẫn”, “Tên tệp”. "vật mẫu. txt”, “Thư mục”. ”thư mục”}…]

1. Chuyển hướng người dùng đến microservice

Sample: ``` javascript function downloadZip(fileIds) Test_logged_in() files = Lookup_file_details_or_panic()// Encode files and save to redis json = JSON.encode(files) ref = Generate_Random_Ref() redis.set( key="zip:"+ref, value=json, expiry=300 )// Redirect the user to the S3Zipper RedirectUser( "https://zipperURL/?ref=" + ref )

Tôi hi vọng nó làm việc cho bạn

Nếu bạn có bất kỳ câu hỏi nào, chỉ cần cho chúng tôi biết trong phần bình luận bên dưới. Tôi hy vọng ai đó ở đâu đó thấy mã này hữu ích và vui lòng nếu bạn làm thế, chỉ cần nói xin chào (hoặc đến làm việc với chúng tôi). Vui thích

Bạn có thể nén tệp trong S3 không?

Các tệp nén S3 . Takes an amazon s3 bucket folder and zips it to a: Luồng . Tệp cục bộ . Phân đoạn tệp cục bộ (zip nhiều tệp được chia nhỏ theo số lượng tệp hoặc kích thước tối đa)

Làm cách nào để cung cấp bản tải xuống zip của gói tệp S3 một cách an toàn?

Chạy “go s3zipper. go” và duyệt đến http. //máy chủ cục bộ. 8000/?ref=test . Các tệp của bạn sẽ được tải xuống dưới dạng tệp Zip ngay lập tức. Bây giờ, bạn chỉ cần chạy ứng dụng này trên máy chủ và yêu cầu ngôn ngữ phía máy chủ của bạn đặt các định nghĩa tệp vào bộ nhớ, sau đó chuyển hướng người dùng đến microservice.

S3 có tự động nén dữ liệu không?

Khi một đối tượng mới được tải vào S3, có một tùy chọn "nén" trong API tự động nén tệp này trước khi tải nó vào S3. This would save customer money in storage costs. This does not exist as a built-in feature of S3.

Tôi có thể đọc tệp S3 mà không cần tải xuống không?

Đọc các đối tượng mà không cần tải chúng xuống . using the S3 resource method put(), as demonstrated in the example below (Gist).