文件上传下载,说来悲剧。
以前都是用的ADF\OAF\JALOR等框架开发,SE搭好工程,只负责业务逻辑开发,基本没怎么想过为什么。
直到一天,为了防止系统入侵,做文件上传现在过滤拦截,才想到一个问题。我们上传下载的原理到底是什么?一直再copy代码,为什么要这么做哪?
我们用一个servlet去处理一个上传文件,看下代码很简单,基本是从request读取流,然后通过IO写入到指定目录,或者通过一些中间件写入一些分布式文件平台。
那么文件是怎么序列化为流数据分装在HTTP报文中的哪?
其实我们的文件上传怎么都要经历如下链路:
1、浏览器解析文件,打包成数据流。
2、通过管道和服务器连接,通过http传输数据到后台。
3、java程序(也可能是其他程序)做IO处理,做文件写操作。
enctype:属性规定在发送到服务器之前应该如何对表单数据进行编码。
form表单中enctype属性可以用来控制对表单数据的发送前的如何进行编码,
enctype有三种,分别为:
multipart/form-data不对字符编码,用于发送二进制的文件,其他两种类型不能用于发送文件;
text/plain用于发送纯文本内容,空格转换为 “+” 加号,不对特殊字符进行编码,一般用于email之类的;
application/x-www-form-urlencoded,在发送前会编码所有字符,即在发送到服务器之前,所有字符都会进行编码(空格转换为 “+” 加号,"+"加号转换为空格,特殊符号转换为 ASCII HEX 值)。
其中application/x-www-form-urlencoded为默认类型。
看下文件上传的报文,就这么一个file控件,以CSDN上传头像为例,浏览器会自动把上传文件解析如下:
Request URL: https://me.csdn.net/api/user/uploadBase64Avatar
Request Method: POST
Status Code: 200
Remote Address: 39.96.132.69:443
Referrer Policy: unsafe-url
access-control-allow-credentials: true
access-control-allow-headers: Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Data-Type,X-Requested-With
access-control-allow-methods: GET,PUT,POST,DELETE,OPTIONS
access-control-allow-origin: https://i.csdn.net
content-type: application/json; charset=UTF-8
date: Sun, 13 Oct 2019 09:26:34 GMT
server: openresty
status: 200
Provisional headers are shown
Accept: application/json, text/javascript, /; q=0.01
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryV5ocEPiRQuRh2vcB
Origin: https://i.csdn.net
Referer: https://i.csdn.net/
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
avatar_file: (binary)
a:
最下面是POST请求封装的Form Data,我们会发现浏览器有自动把数据解析成二进制的数据的功能。
具体数据如下:
------WebKitFormBoundaryV5ocEPiRQuRh2vcB
Content-Disposition: form-data; name=“avatar_file”; filename=“avatar_file.png”
Content-Type: image/png
‰PNG
IHDR––<qâ IDATx^ì½”]gy.ü|»ï}z›3]Ó$z±%[–{ƒÁ—Ä6¡òJnˆ —rC
……………………………………………………
------WebKitFormBoundaryV5ocEPiRQuRh2vcB
Content-Disposition: form-data; name=“a”
------WebKitFormBoundaryV5ocEPiRQuRh2vcB–
中间内容省略
二、服务器
1.服务器端程序收到"multipart/form-data"类型的http请求消息
2.读取这个请求消息里面的实体内容
3.解析每个分区的数据
4.从每个分区中解析出描述头和主体内容部分
@RequestMapping("/uploadFile")
public void uploadFile(HttpServletRequest request, HttpServletResponse response) {
ProgressListener progressListener = new ProgressListener() {
@Override
public void update(long l, long l1, int i) {
}
};
String savePath = "D:\\faceImage\\";
File file = new File(savePath);
if (!file.exists() && file.isDirectory()) {
System.out.println(savePath + "目录不存在,需要创建");
file.mkdir();
}
try {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setHeaderEncoding("UTF-8");
if (!ServletFileUpload.isMultipartContent(request)) {
//
}
upload.setProgressListener(progressListener);
List<FileItem> fileItemList = upload.parseRequest(request);
fileItemList.forEach(item -> {
if (item.isFormField()) {
//普通的参数对应的读取
String name = item.getFieldName();
try {
String value = item.getString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
//如果fileupload中是上传文件
String fileName = item.getName();
if (fileName == null || fileName.trim().equals("")) {
return;
}
if (fileName.contains("\\")) {
fileName = fileName.substring(fileName.lastIndexOf("\\" + 1));
}
try {
InputStream inputStream = item.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream(savePath + "\\" + fileName);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer);
}
inputStream.close();
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}