ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

http 文件上传数据格式及基于 golang 的文件接收服务实现

2022-05-29 23:34:48  阅读:262  来源: 互联网

标签:文件 http log err part file 数据格式 data


背景

最近在实现一个文件上传的需求,学习了一下 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-Typemultipart/form-data 说明了这个请求的请求体可能会包含多个部分的数据。不同的部分会用 boundary 声明的字段作为分界线。示例请求即用 BbC04y 作为分界。从示例中可以看到,最终结束部分的分界,是 ---BbC04y--- 的格式。而除最终结束的分界,都是 ---BbC04y 的格式。这点需要额外注意。

从示例中可以看到,除了上传文件数据,还可以上传其他类型的数据。每个部分都有一个 name 字段用以做相应的标志。后台可以基于该字段区分上传的数据。示例中就在第一个部分携带了一个 json 格式的数据,name 设置为 meta-data
而示例的第二部分(name="file")携带的即是文件数据,假设上传的是一个图片,则这个部分就是图片的原始数据。

基于 golang 的文件接收服务

了解了 http 协议如何进行文件上传后,下面展示一个 demo,展示如何编写一个服务端读取文件信息,写入本地。

读取文件信息,主要是使用 http.RequestMultipartReader() 方法,获得 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))
}

实现时有几点需要注意的是

  1. 需要声明读取数据的 buf size,这个不宜设置过大,否则一次性读取太多数据到内存,也不宜设置过小,否则会轮询读取太多次。golang 官方包的 io.Copy 方法,设置的默认大小是 32*1024 也就是 32KB
  2. 由于 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有