前端使用JS发起HTTP请求同时提交文件和JSON数据

前端使用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()方法的参数就行。

沙发