HTTP文件上传
简单来说,文件上传可分为三步:
- 组织请求体
- 设置Content-Type
- 发送Post请求
组织请求体
Request Body(请求体)常用于Post请求上,Get请求也可以有,只是约定俗成的服务端一般会忽略调GET的请求体
设置Content-Type
用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件
常见的Content-Type有如下几种:
- application/x-www-form-urlencoded:默认的表单提交的格式,是将表单中的数据按照urlencoded方式返送,类型于get请求中所带的参数
//模拟表单参数
data := make(url.Values)
data.Set("username", "aaa")
data.Set("password", "123")
fmt.Println(data.Encode()) //password=123456&username=poloxue
- application/json:以json的格式提交数据
- text/xml:以xml的格式提交数据
- text/plain:纯文本的数据提交方式
- application/octet-stream:二进制流的方式提交数据
文件上传
文件上传可以看成是表单提交的一种特殊的形式,表单提交默认的是使用Content-Type:application/x-www-form-urlencoded
如下是html表单提交的方式:
如下是go模拟表单提交的方式:
data := make(url.Values)
data.Set("username", "poloxue")
data.Set("password", "123456")
// 按 urlencoded 组织数据
body, _ := data.Encode()
//发送post请求
http.Post(
"http://httpbin.org/post",
"application/x-www-form-urlencoded",
bytes.NewReader(body),
)
默认的表单提交方式是不支持文件上传的,因为文件需要通过二进制的方式进行传送,而表单提交的Content-Type:application/x-www-form-urlencoded
并不是二进制方式发送数据的,
当然也可以通过body中二进制流的方式实现,但这种不能发送多个文件
RFC 1867添加了对文件上传的支持,它主要的内容有:
- input标签的类型增加一个file选项
- form表单的encrypt 增加
multipart/form-data
如下是一个支持文件上传的form表单:
在Get中查询参数之间是通过&
来区分的,所以当通过Post上传多个文件时是通过boundary来区分的,boundary是即时
生成的字符串,用以确保整个分隔符不会在文件或表单项的内容中出现,之所以使用boundary区分,而不是使用&
,是因为防止与文件内容冲突
文件上传的实现
客户端代码:
func main() {
buf := new(bytes.Buffer)
writer := multipart.NewWriter(buf)
//fieldname:服务器通过这个参数接收文件,filename:接收到文件后的文件名
formFile, err := writer.CreateFormFile("file", "license")
if err != nil {
log.Fatalf("Create form file failed: %s\n", err)
}
// 打开要上传的文件
srcFile, err := os.Open("D:\\temp\\license.txt")
if err != nil {
log.Fatalf("%Open source file failed: s\n", err)
}
defer srcFile.Close()
//将文件内容写入
_, err = io.Copy(formFile, srcFile)
if err != nil {
log.Fatalf("Write to form file falied: %s\n", err)
}
//设置发送的Content-Type
contentType := writer.FormDataContentType()
writer.Close()
//发送post请求上传文件
_, err = http.Post("http://localhost:8080/file", contentType, buf)
if err != nil {
log.Fatalf("Post failed: %s\n", err)
}
}
服务器端:
func main() {
http.HandleFunc("/file", uploadHandle)
http.ListenAndServe(":8080", nil)
}
func uploadHandle(w http.ResponseWriter, r *http.Request) {
// 获取文件
formFile, header, err := r.FormFile("file")
if err != nil {
log.Printf("Get form file failed: %s\n", err)
return
}
defer formFile.Close()
//此处的header.Filename就是客户端的filename
destFile, err := os.Create(header.Filename)
if err != nil {
log.Printf("Create failed: %s\n", err)
return
}
defer destFile.Close()
_, err = io.Copy(destFile, formFile)
if err != nil {
log.Printf("Write file failed: %s\n", err)
return
}
}
Http文件下载
func downloadFile(url string,filepath string) error {
resp,err := http.Get(url)
if err != nil{
log.Printf("download file err:%v",err)
return err
}
if resp == nil || resp.Body == nil{
return fmt.Errorf("file is null")
}
defer resp.Body.Close()
//create file
f,err := os.Create(filepath)
if err != nil{
log.Printf("create file err:%v",err)
return err
}
_,err = io.Copy(f,resp.Body)
if err != nil{
return err
}
return nil
}