前端使用Javascript发起一个带文件和其他参数得HTTP请求,一般使用Multipart
请求。
Multipart
请求,可以在HTTP的请求体中,分割出多个子请求体,各个子请求体可以有自己的body和Header
FormData
Js提供的对象,它类似于一个Map,可以添加多个数据。使用FormData
作为HTTP请求的Body,每一个添加到FormData
的数据,都会被编码成一个子请求体。
对象参考
Demo
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input name="file" type="file" onchange="upload(event)">
</body>
<script>
function upload (e){
let files = e.target.files
if (!files) {
return
}
let formData = new FormData();
// 添加普通的form表单参数子body
formData.append("title", "SpringBoot中文社区")
// 添加JSON子body,并且为这个body设置一个ContentType=application的header
formData.append("config", new Blob([JSON.stringify({site: "https://springboot.io", })], {type: "application/json; charset=utf-8"}))
// 添加文件子body
formData.append("log", files[0])
fetch('/upload', {
method: 'POST',
body: formData // 设置Body参数
})
.then(resp => resp.text())
.then(message => console.log(message))
.catch(err => {
throw err
})
}
</script>
</html>
服务器
使用Go编写提供一个HTTP服务,解析Multipart
请求,把Body和所有Header信息打印出来
package main
import (
"encoding/json"
"html/template"
"io"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
tpl, _ := template.ParseFiles("index.html")
writer.WriteHeader(http.StatusOK)
writer.Header().Set("Content-Type", "text/html; charset=ut-8")
tpl.Execute(writer, nil)
})
http.HandleFunc("/upload", func(writer http.ResponseWriter, request *http.Request) {
reader, err := request.MultipartReader()
if err != nil {
// 获取Reader异常
log.Fatalln(err.Error())
}
// 迭代所有的body
for true {
part, err := reader.NextPart()
if err != nil {
if err == io.EOF {
break
}else {
log.Fatalf(err.Error())
}
}
func (){
defer func() {
if err := part.Close(); err != nil {
log.Fatalf(err.Error())
}
}()
// 请求头
headers := part.Header
jsonVal, _ := json.Marshal(headers)
log.Printf("Header: %s\n", jsonVal)
// 表单名称
formName := part.FormName()
log.Printf("FormName: %s\n", formName)
// 文件名字
fileName := part.FileName()
log.Printf("FileName: %s\n", fileName)
// 读取Body
content, err := io.ReadAll(part)
if err != nil {
// TODO 读取body异常
}
log.Printf("Body: %s\n", content)
}()
}
writer.WriteHeader(http.StatusOK)
writer.Header().Set("Content-Type", "text/plain; charset=ut-8")
writer.Write([]byte("success"))
})
http.ListenAndServe(":80", nil)
}
测试日志
客户端日志截图
服务端日志输出
2021/05/16 21:47:39 Header: {"Content-Disposition":["form-data; name=\"title\""]}
2021/05/16 21:47:39 FormName: title
2021/05/16 21:47:39 FileName:
2021/05/16 21:47:39 Body: SpringBoot中文社区
2021/05/16 21:47:39 Header: {"Content-Disposition":["form-data; name=\"config\"; filename=\"blob\""],"Content-Type":["application/json; charset=utf-8"]}
2021/05/16 21:47:39 FormName: config
2021/05/16 21:47:39 FileName: blob
2021/05/16 21:47:39 Body: {"site":"https://springboot.io"}
2021/05/16 21:47:39 Header: {"Content-Disposition":["form-data; name=\"log\"; filename=\"58c4e5c7fd6714e4fa7cc5527d9091080207633d.png\""],"Content-Type":["image/png"]}
2021/05/16 21:47:39 FormName: log
2021/05/16 21:47:39 FileName: 58c4e5c7fd6714e4fa7cc5527d9091080207633d.png
2021/05/16 21:47:39 Body: �PNG
�J�d�j��k<�|m\AM �k�NRֶ�l�-��h�x�jY,�oq��&.�~���?8��@��S��[k�(���%dأ'\�o>�ff���L�L�� {?Q�����L[��BB���>��C��E���S�Y��`���<!n�e���3��{,���x���o��"c,b<�eBȸ� �7�,7#H�g�q� 2^�)��"Ȳ����S�f��c��iY�yz�͓�439,���G��c��ͼ�gۃN�qr��Z�q�CĪ���Z�$t$�ԅ�{9z�%2F�Y��2�s�;xϓ��)�m�N��Ren���ĤZ�a��`��<x��s�A�h��=�����d��'�8�s)'8�!��o�L��h�����'��0�����v������*j����_5��L-u�8�nq� �E&)��mKԆi��Yh�b�#Gô��L�eI�{����/�}��� IEND�B`�
准确的解析到了前端的文件数据和其请求体数据
由于直接对图片文件解码为字符串,所以产生了乱码
最后
如果使用XMLHttpRequest
发起请求也一样,使用FormData
作为send()
方法的参数就行。