前言

在开发的过程中,难免会碰到上传/下载文件的场景。如果这个功能服务端没开发好,就没法进行测试。

所以每次都要自己先实现一遍,本文使用 go 写了一个简单的文件上传和下载服务。

以后再碰到这种场景的时候就可以快速进行开发。

下载

因为下载比较简单, go 语言默认就有,所以先来说一下下载。

熟悉 go 的都知道, go 对文件服务器有默认的支持,所以我们直接调用相关的 API 就能完成对应的操作。

1
2
http.Handle("/", http.FileServer(http.Dir("/tmp/Downloads")))
http.ListenAndServe(":8080", nil)

在这里需要用 http 包中的 FileServer 函数,它需要一个文件夹,我这里使用的是一个临时目录 /tmp/Downloads

接着需要把这个 Handler 绑定到一个路由中,这里绑定的就是 /

最后需要启动这个服务,我把这个服务绑定到 8080 端口,然后使用 go run main.go 就可以运行起服务器了。

你可以通过 http://localhost:8080/ 来查看 /tmp/Downloads 下所有的文件。

上传

与下载不同的是, go 没有提供默认的实现(也许有,我不知道),所以我们需要自己实现。

实现文件上传也比较简单

 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
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Receive Upload: ", time.Now())
    file, header, err := r.FormFile("file")
    if err != nil {
        fmt.Println("Failed to get file data ", err.Error())
        _, _ = io.WriteString(w, err.Error())
        return
    }
    defer file.Close()
    newFile, err := os.Create(header.Filename)
    if err != nil {
        fmt.Println("Failed to create file ", err.Error())
        return
    }
    defer newFile.Close()
    _, err = io.Copy(newFile, file)
    if err != nil {
        fmt.Println("Copy ", header.Filename, " to ", newFile.Name(), "failed ", err.Error())
        return
    }
    fmt.Println("Upload File Success ", newFile.Name(), ",", time.Now())
}

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

我们把上传的逻辑放到 uploadHandler 这个函数中,并绑定到 /upload 这个路由上。

uploadHandler 中会从表单中读取 file 的文件,然后用它在本地创建一个同名的文件,接着把内容拷贝到新创建的本地文件中。这样就完成了。

现在我们就可以使用 curl 来测试一下上传文件

1
2
3
curl -X POST http://localhost:8080/upload \
  -F "file=@/tmp/upload.go" \
  -H "Content-Type: multipart/form-data"

不出所料,执行完成之后,在当前文件夹下多出了 upload.go 这个文件。

完整代码

 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
35
36
37
38
39
package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("Receive Upload: ", time.Now())
    file, header, err := r.FormFile("file")
    if err != nil {
        fmt.Println("Failed to get file data ", err.Error())
        _, _ = io.WriteString(w, err.Error())
        return
    }
    defer file.Close()
    newFile, err := os.Create(header.Filename)
    if err != nil {
        fmt.Println("Failed to create file ", err.Error())
        return
    }
    defer newFile.Close()
    _, err = io.Copy(newFile, file)
    if err != nil {
        fmt.Println("copy ", header.Filename, " to ", newFile.Name(), "failed ", err.Error())
        return
    }
    fmt.Println("Upload File Success ", newFile.Name(), ",", time.Now())
}

func main() {
    fmt.Println("Start Go Download Upload Server...")
    http.HandleFunc("/upload", uploadHandler)
    http.Handle("/", http.FileServer(http.Dir("/tmp/Downloads")))
    http.ListenAndServe(":8080", nil)
}