关于一个打包下载的需求
前些天遇到一个「打包下载」的需求,在调研过程中走了一些弯路,本文记录一下。
比如说某网站有一个文件列表,用户点哪个就可以下载哪个,如果用户想下载多个,无非就是多点几次而已。于是需求来了:当用户想下载多个文件的时候,可以通过一次点击完成打包下载操作。
听起来似乎并不复杂,服务端可以把用户想要下载的文件打包成一个新文件,然后用户点一次就可以下载了,但是这样做有以下几个缺点:
浪费了时间,多了创建新文件的流程。 浪费了空间,同样的文件被多次存储。 用户体验差,下载必须要等到新文件创建好才能开始。不难得出结论:动态流式下载才是正解,同事提到 tar 可以搞定,于是研究一下:
shell> cat test_0.txt xxx xxx shell> cat test_1.txt yyy yyy shell> tar cf test.tar test_0.txt test_1.txt shell> cat test.tar test_0.txt00006440...01014257504126011510 0ustar rootrootxxx xxx test_1.txt00006440...01014257504241011507 0ustar rootrootyyy yyy
如上可见,tar 文件的格式非常简单,多个文件的内容从上到下依次排列,只不过每个文件内容的前面附加了一个头,其中保存了诸如文件名,权限之类的信息。
看上去用 tar 的话确实可以搞定动态流式下载,不过 tar 有个缺点,普通用户搞不清 tar 文件类型是什么东西,相比较而言,他们更乐于接受 zip 文件类型。
不过 zip 文件类型的格式可要比 tar 复杂,我从 wikipedia 找到下图:
对于凡夫俗子的我来说,想要通过手撸 zip 格式来实现动态流式下载绝非易事,就在举棋不定之际,我突然发现 golang 的 zip 标准库已经实现了 Writer 接口,这就意味着,我们只要结合使用 zip.NewWriter 和 http.ResponseWriter 就能实现我们的目的:
package main import ( "archive/zip" "fmt" "io" "net/http" "os" ) func main() { http.HandleFunc("/test", test) http.ListenAndServe(":8080", nil) } func test(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/zip") w.Header().Set("Content-Disposition", "attachment; filename=test.zip") writer := zip.NewWriter(w) for i := 0; i < 2; i++ { name := fmt.Sprintf("test_%d.txt", i) srcFile, err := os.Open(name) if err != nil { panic(err) } defer srcFile.Close() dstFile, err := writer.Create(name) if err != nil { panic(err) } if _, err := io.Copy(dstFile, srcFile); err != nil { panic(err) } } writer.Close() }
如上代码编译运行后,打开浏览器,执行 http://localhost:8080/test 即可看到效果。
文章来源:
Author:老王
link:https://blog.huoding.com/2022/07/01/984
上一篇:一个unicode问题