最近的一个小项目里使用到了文件上传、下载功能,今天我打算梳理一下文件上传所涉及的技术及实现。 内容主要包括两部分,如何通过纯 Servlet 的形式进行文件上传、保存(不通过 Spring 框架);另一部分是如何在 Spring Web MVC 中进行文件上传。
01-从 HTTP 协议角度分析文件上传
HTTP 协议传输文件一般都遵循 RFC 1867 规范,即客户端通过 POST 请求,Context-Type 为 "multipart/form-data"。 前端提交页面一般为:
<form method="post" action="${user_upload_service_url}" enctype="multipart/form-data">
Choose a file: <input type="file" name="image" accept="image/*" />
<input type="submit" value="Upload" />
</form>
通过 Wireshark 对 POST 请求进行抓包,发现发送的请求格式为:
POST /upload HTTP/1.1
Host: localhost:8080
Content-Length: 197624
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynIbwtdWznj6QLu52
First boundary: ------WebKitFormBoundarynIbwtdWznj6QLu52
Encapsulated multipart part: (image/png)
Content-Disposition: form-data; name="image"; filename="Snipaste_2023-01-05_13-35-11.png"
Content-Type: image/png
Portable Network Graphics
Boundary: ------WebKitFormBoundarynIbwtdWznj6QLu52
Encapsulated multipart part: (image/png)
Content-Disposition: form-data; name="image"; filename="Snipaste_2023-01-05_13-35-12.png"
Content-Type: image/png
Portable Network Graphics
Last boundary: ------WebKitFormBoundarynIbwtdWznj6QLu52--
对上述过程有了基本的理解后,就可以动手来写上传功能(本文以图片为例,当然你也可以实现支持上传其他类型的文件的版本)。 接下来我会展示两种实现文件上传功能的代码,第一种是使用纯 Servlet API 实现,不依赖 Spring 框架,当你的程序是一个简单的基于 Servlet 的应用时,可以参考这种方式。 第二种,借助了 Spring 提供的 MultipartFile 以及 MultipartResolver 实现的文件上传。
02-Servlet 处理上传请求
首先,需要先实现一个 Servlet。
@MultipartConfig(fileSizeThreshold = 5 * 1024 * 1024,
maxFileSize = 1024 * 1024 * 5,
maxRequestSize = 1024 * 1024 * 5)
@WebServlet(name = "MultipartServlet", urlPatterns = "/servlet-upload")
public class MultipartServlet extends HttpServlet {
private File uploadDir = null;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// 检查存储文件的路径是否存在,若不存在,则创建一个
String uploadPath = System.getProperty("user.dir") + File.separator + "uploads";
uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 第一节中介绍过,文件上传是通过 POST 方法完成的,所以这里我们要重写 doPost 方法
try {
final Collection<Part> parts = req.getParts(); // 从请求中获取 multipart 内容
for (Part part : parts) {
if (part.getSize() <= 0) { // 判断上传的内容是否空文件
System.out.println("part is empty, skip it!");
continue;
}
String fileName = getFileName(part); // 从请求中获取文件的名
// or
//final String fileName = part.getSubmittedFileName();
// fileName 是前端提供的,并不十分可靠
// 后端应该自己生成一个文件名
fileName = genNewFileName(fileName);
String uploadedFilePath = uploadDir + File.separator + fileName;
part.write(uploadedFilePath); // 存储到指定目录
System.out.println("saved to " + uploadedFilePath);
resp.getWriter().write("saved to " + uploadedFilePath);
}
} catch (ServletException se) {
// request is not of type multipart/form-data
}
resp.setStatus(HttpServletResponse.SC_OK);