文件上传
前端部分
首先我们写一个文件上传的表单页面:
1 2 3 4 5
| <form method="post" enctype="multipart/form-data" action="Upload"> <input type="file" name="photo"> <br> 用户名<input type="text" name="username"><br> <input type="submit" name="submit"> </form>
|
当我们使用表单进行传输数据的时候,需要设定属性 enctype="multipart/form-data"
这是因为对于文件来说,不能像 XXX=XXX&XXX=XXX
这样的 Post 方式传送信息,所以需要使用 multipart 多块传送,将每一组信息都放到一个数据块里面提交。
这个表单有着血泪史,一定要记得在提交数据的时候一定要些 name 属性,不然组不成 name=value
的格式,就没有办法用 Post 传输数据。然后点击这个表单上传文件并提交之后,我们来看看发出来的 HTTP 请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| POST http://localhost:8080/BookStore/Upload HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------16694935940386624681704157384 Content-Length: 36544 Origin: http://localhost:8080 Connection: keep-alive Referer: http://localhost:8080/BookStore/test.jsp Cookie: JSESSIONID=36C32D4D34BDD9D1138C898DA0C86440; Idea-b21bbe90=38338ff0-a6c6-49f8-b92a-980001c9c1a6 Upgrade-Insecure-Requests: 1
-----------------------------16694935940386624681704157384 Content-Disposition: form-data; name="photo"; filename=".......jpg" Content-Type: image/jpeg
.PNG . ....IHDR.......m......E.... .IDATx^.....E..;"....@...C..... ....h,... .&."".]Y@...Q.(.`...rD...a.v.%x....,.%..d.r.D.C..=.._;....y.{f...T................./.d. ...@...... ...@...&!.{=.....@...... ...@ #.8. -----------------------------16694935940386624681704157384 Content-Disposition: form-data; name="username"
Xorex -----------------------------16694935940386624681704157384 Content-Disposition: form-data; name="submit"
............ -----------------------------16694935940386624681704157384--
|
然后我们来分析一波,下面这个头表示用表单设置出来的,不同的是多了一个 boundary 属性,是浏览器自己生成的随机数分割线,用来分隔 POST 传送的不同的数据块的。
1
| Content-Type: multipart/form-data; boundary=---------------------------16694935940386624681704157384
|
然后,这个是图片数据块的开头,标识数据名 name="photo"
和文件名 filename=".......jpg"
(这里因为编码原因无法显示),然后下面的 Content-Type: image/jpeg
作为请求头组成之一标识这个数据块的数据类型为图片,且是 jpeg 格式。
1 2 3
| -----------------------------16694935940386624681704157384 Content-Disposition: form-data; name="photo"; filename=".......jpg" Content-Type: image/jpeg
|
设置好了前端的文件上传的数据发送,就要设置好后端的数据接收。
后端部分
这里需要导入两个 Maven 依赖:
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>1.4</version> </dependency>
|
这里的处理思路主要是先判断是否是 Multipart 类型的数据,如果是则进行处理。然后用 ServletFileUpload 的实例去解析 request 请求里面的数据块,它会将所有的数据块都解析成一个 FileItem 实例,然后封装成一个 List 之后传出来。我们遍历所有的数据块 FileItem ,然后选取不是来自于 Filed 的普通文字标段字段(也就是文件啦),去把这个文件里面的所有内容写入一个 File 里面即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class UploadService extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if(ServletFileUpload.isMultipartContent(req)) { FileItemFactory fileItemFactory=new DiskFileItemFactory(); ServletFileUpload servletFileUpload=new ServletFileUpload(fileItemFactory); try { List<FileItem> list = servletFileUpload.parseRequest(req); for(FileItem i:list) { if(!i.isFormField()) { i.write(new File("E:/"+i.getName())); } } } catch (Exception e) { e.printStackTrace(); }
} else { resp.setCharacterEncoding("UTF-8"); resp.setHeader("Content-Type","text/html; charset=UTF-8"); PrintWriter writer = resp.getWriter(); writer.println("<h1>上传失败哦</h1>"); resp.setHeader("Refresh", "5; url=/BookStore/test.jsp"); } } }
|
文件下载
Servlet 编写
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class DownloadService extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String downloadName="waifu.jpg"; ServletContext context=getServletContext(); String MimeType=context.getMimeType("/static/img/"+downloadName); resp.setContentType(MimeType); resp.setHeader("Content-Disposition", "attachment; fileName="+downloadName); InputStream inputStream=context.getResourceAsStream("/static/img/"+downloadName); OutputStream outputStream=resp.getOutputStream(); IOUtils.copy(inputStream, outputStream); } }
|
乱码处理
因为 HTTP 协议在设计的时候就没有考虑到中文的问题,所以就有可能会出现中文文件名乱码。对于访问的客户端我们可以分为火狐浏览器和非火狐浏览器,对于非火狐浏览器,我们只需要对涉及到中文的地方进行 URL 编码即可,浏览器会自动对 URL 编码进行 URL 解码和 UTF-8 解码获取中文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class DownloadService extends HttpServlet {
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String downloadName="Waifu.jpg"; ServletContext context=getServletContext(); String MimeType=context.getMimeType("/static/img/"+downloadName); resp.setContentType(MimeType); resp.setHeader("Content-Disposition", "attachment; fileName=内存.jpg"+URLEncoder.encode("内存.jpg", "UTF-8")); InputStream inputStream=context.getResourceAsStream("/static/img/"+downloadName); OutputStream outputStream=resp.getOutputStream(); IOUtils.copy(inputStream, outputStream); } }
|
对于火狐浏览器,就需要对文件名进行更特殊的编码了,格式为:=?charset?B?XXXX?=
。其中 =?
和 ?=
本别标识开始和结束,charset
表示字符的编码方式,B
表示使用 Base64 编码(因为火狐默认用的 Base64 解码后面的东西),XXXX
表示我们用 Base64 加密的 UTF-8 汉字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class DownloadService extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String returnName; if(req.getHeader("User-Agent").contains("FireFox")) { returnName="=?utf-8?B?"+String.valueOf(Base64.getEncoder().encode("内存.jpg".getBytes(StandardCharsets.UTF_8)))+"?="; System.out.println(returnName); } else { returnName=URLEncoder.encode("内存.jpg", "UTF-8"); } String downloadName="Waifu.jpg"; ServletContext context=getServletContext(); String MimeType=context.getMimeType("/static/img/"+downloadName); resp.setContentType(MimeType); resp.setHeader("Content-Disposition", "attachment; fileName="+returnName); InputStream inputStream=context.getResourceAsStream("/static/img/"+downloadName); OutputStream outputStream=resp.getOutputStream(); IOUtils.copy(inputStream, outputStream); } }
|