http 文件上传数据格式及基于 golang 的文件接收服务实现
作者:互联网
背景
最近在实现一个文件上传的需求,学习了一下 http 进行文件上传时的请求数据结构,以及如何基于 golang 实现服务端获取文件信息并存到本地。
http 文件上传
基于 http 的文件上传,主要是利用 http 协议中的 multipart/form-data
这个 Content-Type
。利用它上传文件时,其请求体结构如下:
POST /test HTTP/1.1
Host: foo.example
Content-Type: multipart/form-data;boundary="BbC04y"
--BbC04y
Content-Disposition: form-data; name="meta-data"
{
"reqeust_id" : "abdefg"
}
--BbC04y
Content-Disposition: form-data; name="file"
...file value
--BbC04y--
Content-Type
为 multipart/form-data
说明了这个请求的请求体可能会包含多个部分的数据。不同的部分会用 boundary
声明的字段作为分界线。示例请求即用 BbC04y
作为分界。从示例中可以看到,最终结束部分的分界,是 ---BbC04y---
的格式。而除最终结束的分界,都是 ---BbC04y
的格式。这点需要额外注意。
从示例中可以看到,除了上传文件数据,还可以上传其他类型的数据。每个部分都有一个 name
字段用以做相应的标志。后台可以基于该字段区分上传的数据。示例中就在第一个部分携带了一个 json 格式的数据,name 设置为 meta-data
。
而示例的第二部分(name="file"
)携带的即是文件数据,假设上传的是一个图片,则这个部分就是图片的原始数据。
基于 golang 的文件接收服务
了解了 http 协议如何进行文件上传后,下面展示一个 demo,展示如何编写一个服务端读取文件信息,写入本地。
读取文件信息,主要是使用 http.Request
的 MultipartReader()
方法,获得 multipart.Reader
。成功获取之后,再调用 NextPart()
方法, 即可按需得到对应的 part 的multipart.Part
实例。
func handler(w http.ResponseWriter, r *http.Request) {
reader, _ := r.MultipartReader()
metadata, _ := reader.NextPart()
filepart, _ := reader.NextPart()
...
}
multipart.Part
提供了 FormName
来获取对应部分的名字,实现了 Read
接口方法来读取其中的数据。下面以上述的 HTTP 示例请求为例,编写服务端的代码:
package main
import (
"encoding/json"
"io"
"log"
"mime/multipart"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
reader, err := r.MultipartReader()
if err != nil {
log.Println("get multi part reader error: ", err.Error())
return
}
metadata, err := reader.NextPart()
if err != nil {
log.Println("get metadata part error: ", err.Error())
return
}
readMetaData(metadata)
log.Println("metadata part name: ", metadata.FormName())
filepart, err := reader.NextPart()
if err != nil {
log.Println("get file part error: ", err.Error())
return
}
log.Println("file part name: ", filepart.FormName())
readFile(filepart)
w.Write([]byte("request successfule"))
}
func readMetaData(part *multipart.Part) {
dataCache := make([]byte, 1024)
n, err := part.Read(dataCache)
if err != nil && err != io.EOF {
log.Println("read meta data error. ", err.Error())
return
}
data := &MetaData{}
err = json.Unmarshal(dataCache[:n], data)
if err != nil {
log.Println("json unmarshal error. ", err.Error())
return
}
log.Printf("read meta data %+v", data)
}
func readFile(part *multipart.Part) {
f, err := os.Create("temp.png")
if err != nil {
log.Println("create file error. ", err.Error())
return
}
buf := make([]byte, 1024)
for {
n, err := part.Read(buf)
if err != nil && err != io.EOF {
log.Println("read file data error. ", err.Error())
return
}
if err == io.EOF || n == 0 {
break
}
_, err = f.Write(buf[:n])
if err != nil {
log.Println("write file error. ", err.Error())
return
}
}
log.Println("write file success. ")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
实现时有几点需要注意的是
- 需要声明读取数据的 buf size,这个不宜设置过大,否则一次性读取太多数据到内存,也不宜设置过小,否则会轮询读取太多次。golang 官方包的
io.Copy
方法,设置的默认大小是32*1024
也就是 32KB - 由于
Read
方法在读取数据写到 buffer 时,如果数据小于 buffer size,那么剩余的部分也会被填入空值。因此将 buffer 中的数据写入到本地文件中时,需要利用返回的读取数据 size(n
) 进行截断,即f.Write(buf[:n])
的方式调用。否则多余的空值也会被写入本地文件中,导致文件错误。
上述的代码会在终端输出如下信息,并将上传的图片文件保存到本地。
$ read meta data &{RequestID:abcdefg}
$ metadata part name: meta-data
$ file part name: file
$ write file success.
标签:文件,http,log,err,part,file,数据格式,data 来源: https://www.cnblogs.com/amoy-zhp/p/16325269.html