LeeYzero的博客

业精于勤,行成于思

0%

使用Go实现批量文件上传

在现在生活中,我们经常在使用文件上传功能,但我们很少会自己去做一个文件上传的服务,了解文件的上传原理有助于我们开发出更健壮的程序。利用Go语言以及其标准库的能力,用很少的代码量就可以实现一个文件上传服务器。本文以小工具的方式讲解如何实现一个简单的文件上传服务器,并假设读者对Go、HTTP、HTML有一定了解。

完整代码参考:github.com/leeyzero/go-tools/uploadserver.go

Server端

文件上传一般使用multipart/form-data上传上传,Go语言标准库其进行了良好的封装,感兴趣的可以直接查询其原码实现。对于文件上传服务器,需要搭建一个简单的服务端框架,幸运的是Go语言http包已经为我们实现了,我们只需要实现一个handler即可,

1
2
3
4
5
6
7
8
func uploadHandler(w http.ResponseWriter, r *http.Request) {
...
}

func main() {
http.HandleFunc("/upload", uploadHandler)
http.ListenAndServe(":8080", nil)
}

在uploadHandler中,只需要进行以下三个步骤:
1、解析multiform data
2、遍历multiform data中的文件
3、将文件保存至指定位置

其中第1步,Go语言的http.Request包已经提供了ParseMultipartform API,我们只需要实现2、3步即可。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func uploadHandler(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(50<<20)
if err != nil {
fmt.Fprintln(w, err)
return
}

files := r.MultipartForm.File["files"]
for i, _ := range files {
file, err := files[i].Open()
if err != nil {
fmt.Fprintln(w, err)
return
}
defer file.Close()

dst, err := os.Create("/tmp/" + files[i].Filename)
if err != nil {
fmt.Fprintln(w, err)
return
}
defer dst.Close()

_, err = io.Copy(dst, file)
if err != nil {
fmt.Fprintln(w, err)
return
}

fmt.Fprintf(w, "upload %s success\n", files[i].Filename)
}
}

在上面的代码中,我们硬编码了一些常量,如监听的端口号,保存上传文件的路径以及上传文件最大允许使用的内存,可以通过环境变量配置出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var (
gAddr string
gTargetDir string
gMaxMemory int64
)

func TryGetEnvString(key string, defVal string) string {
strVal := os.Getenv(key)
if strVal == "" {
strVal = defVal
}

return strVal
}

func TryGetEnvInt64(key string, defVal int64) int64 {
strVal := os.Getenv(key)
if strVal == "" {
return defVal
}

intVal, err := strconv.ParseInt(strVal, 10, 64)
if err != nil || intVal <= 0 {
return defVal
}

return intVal
}

func init() {
gAddr = TryGetEnvString("ADDR", ":8080")
gTargetDir = TryGetEnvString("TARGET_DIR", "/tmp")
gMaxMemory = TryGetEnvInt64("MAX_MEMORY", 50<<20)
}

启动上传服务器,其中监听的端口、上传存储的路径、上传文件使用的最大内存可以分别通过ADDR、TARGET_DIR、MAX_MEMORY环境变量进行配置。如下:

1
ADDR=:8888 go run uploadserver.go

Client端

如果比较喜欢命令行工具,curl是个不错的选择,比如现在要上传test1.txt test2.txt文件:

1
$ curl -F 'files=@test.txt' -F 'files=@test2.txt' 'http://localhost:8080/upload'

也可以使用html写一个简单的表单界面,利用浏览器进行上传,如下upload.html文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>

<head>
<title>Go upload</title>
</head>

<body>
<form action="http://localhost.com:8080/upload" method="post" enctype="multipart/form-data">
<label for="file">选择文件:</label>
<input type="file" name="files" id="file" multiple><br>
<input type="submit" name="submit" value="上传">
</form>
</body>

</html>

将上述upload.html文件用浏览器打开,选择完文件后,点击上传即可将所选文件上传至服务器的指定位置。

总结

本文介绍了如何使用Go实现一个简单的文件上传服务器,Go语言http包已经帮我们做了很多工作,我们只需要解析multiform data并从遍历文件列表,逐步将文件保存到指定的目录即可。标准库给我们封装好用的API的同时,隐藏了multiform-data协议后面的很多细节,对于这些细节,我们不需要面面俱到,但一定要有所了解,这有助于知其然,也知其所以然,参数资料给出进一步的阅读材料。

参考资料

[1] Go http package
[2] HTML input tag
[3] RFC2388 multipart/form-data