以下内容均由java17和springboot3.0演示产生。
从curl的上传方式说起
在curl 命令中,有两种上传文件的方式分别是:
curl --location 'http://10.211.55.2:9888/solution' --form 'file=@"/Users/bo/Downloads/alacritty.yml"' --form 'file2=@"/Users/bo/Downloads/goland-2023.2-aarch64.dmg"'
curl -T file https://example.com
因此会给很多人造成困惑,这两种上传方式有什么区别?以及对应的服务端应该如何接收呢?
方式一对应的http数据包
curl --location 'http://10.211.55.2:9888/solution' \
--form 'file=@"/Users/bo/Downloads/alacritty.yml"' \
--form 'file2=@"/Users/bo/Downloads/goland-2023.2-aarch64.dmg"'
POST /solution HTTP/1.1
Host: 10.211.55.2:9888
Content-Length: 373
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="alacritty.yml"
Content-Type:
(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file2"; filename="goland-2023.2-aarch64.dmg"
Content-Type:
(data)
------WebKitFormBoundary7MA4YWxkTrZu0gW--
首先看第一种上传方式以及对应的http数据包,其中需要注意的是1、4、6、11、16行。其中第四行指定了该http的内容类型为multipart/form-data
,每个文件内容的分隔符为----WebKitFormBoundary7MA4YWxkTrZu0gW
,第六行则是真正的分隔符,需要在head中指定的分隔符前面添加两个--
,第16行为最后一个分隔符,标志着http请求数据包的结束。
以上的这种方式被称为multipart上传,其详细的细节可参考 rfc1867 。由于该上传方式是一种标准的上传方式,因此很多前端库都对其提供了较好的支持,大部分由前端向后端进行的文件上传操作,均是使用这种方式进行上传的。
服务端要如何接收
这里以springboot为例说,简要说明具体的处理逻辑。
spring:
servlet:
multipart:
# 这个参数决定multipart请求的总大小
max-request-size: 5000MB
# 这个参数决定multipart 单个文件的大小(待验证)
max-file-size: 5000MB
# 这个参数决定了是否由springboot帮你处理mutipart请求
# 若是false的话那么你将得到原始的http流
# 若是true的话,你将得到一个空的http流
# 默认是true
# enabled: false
public class SolutionsController {
@PostMapping("/solution")
public void uploadSolution(HttpServletRequest servletRequest) throws Exception {
Enumeration headerNames = servletRequest.getHeaderNames();
if (servletRequest instanceof StandardMultipartHttpServletRequest s) {
//在这里处理所有的文件
s.getFileMap();
}
}
}
springboot对于multipart请求会构造出一个StandardMultipartHttpServletRequest
类型的request对象,我们只需要调用该对象的getFileMap()方法将就能拿到所有的文件名和文件流。
这里需要额外注意,springboot会自动判断文件的大小,若文件较小,则直接将文件存在内存中;否则会将文件写入到一个临时的目录中,待请求结束时候自动删除临时文件
方式二对应的http数据包
curl -T Downloads/alacritty.yml -X POST http://localhost:9888/solution -vv
POST /solution HTTP/1.1
Host: localhost:9888
User-Agent: curl/8.1.2
Accept: */*
Content-Length: 2355
""
这种情况下,我们发现请求变得非常简短,甚至连Content-Type都没有了,并且在这种情况下,还需要额外的参数来指定将上传的文件的保存文件名。
服务端如何接受
这种情况后段要做的就非常简单了,直接获取http对象即可。
public class SolutionsController {
@PostMapping("/solution")
public void uploadSolution(HttpServletRequest servletRequest) throws Exception {
Enumeration headerNames = servletRequest.getHeaderNames();
if (servletRequest instanceof StandardMultipartHttpServletRequest s) {
//在这里处理所有的文件
s.getFileMap();
} else {
// 这里处理普通文件流
servletRequest.getInputStream();
}
}
}
总结
在curl中存在两种上传文件的方式,一种为multipart方式上传,该方式可上传多个文件,同时是一种标准的上传方式,主要用作前端向后段传输数据,因此应用较为广泛。方式二则类似于一种原始二进制流的方式,需要额外的参数来指定文件名类型等信息,这种方式依赖于双方一致的约定,否则可能无法工作。知道上述两种方式的原理之后,就很容易在一个接口上实现对两种上传方式的兼容。