JavaWeb 文件上传和下载
一、文件上传
步骤
前端部分
1.提供一个post方法的表单,并设置enctype
属性(设置请求内容的MIME类型)为multpart/from-data
,代表该表单支持文件上传
2.提供一个文件上传框:<input type="file">
举例:
<form action="/xxx" method="post" enctype="multipart/form-data">
<input type="text" name="description">
<input type="file" name="file">
<input type="submit">
</form>
后端部分
将内容通过流读取进来,举例:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
InputStream in = request.getInputStream();
ServletOutputStream out = response.getOutputStream();
int len = 0;
byte[] b = new byte[1024];
while((len = in.read(b))!=-1){
out.write(b, 0, len);
}
}
结果为:
------WebKitFormBoundary5x8Olc9cfCFQzCFd
Content-Disposition: form-data; name="description"
这是一个测试描述
------WebKitFormBoundary5x8Olc9cfCFQzCFd
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain
这是1.txt的内容
------WebKitFormBoundary5x8Olc9cfCFQzCFd--
fileupload
是apache的commons组件提供的上传组件,主要用于解析上传的输入流内容,比如上面可以看出内容都是上传的各个表单内容的字节流,并且是混杂在一起的,而该组件则可以对这些流进行分类和处理。
使用步骤
1.添加相关jar包——commons-fileupload
/commons-io
(放到WEB-INF/lib
下)
2.实例化工厂类DiskFileItemFactory()
对象
3.实例化工厂解析类ServletFileUpload()
对象
4.调用解析类对象的parseRequest()
方法进行解析,里面直接传入request
对象即可,返回的是一个List<FileItem>
对象(解析前可以先通过isMultipartContent()
方法来判断传入的request对象是否支持文件上传)
核心对象
1.DiskFileItemFactory
工厂类
2.ServletFileUpload
用于解析工厂对象的解析类
3.FileItem
代表表单项,比如下面这样就是一个FileItem:
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain
这是1.txt的内容
其主要提供了以下方法:
(1)isFormField()
:判断是否为普通文本字段,如果返回false,表明是文件字段
(2)getContentType()
:获取上传文件的类型
其中对于普通表单项(text
、password
等)提供了以下方法:
(1)getFieldName()
:获取字段名
(2)getString()
:获取值,可以传入编码参数来设置编码
对于上传的文件提供了以下方法:
(1)getInputStream()
:获取文件输入流
(2)getName()
:获取文件名
(3)getSize()
:获取文件大小
(4)write(File)
:把上传的文件内容保存到指定文件中
上传并输出文件示例
public void doGet(HttpServletRequest request, HttpServletResponse response)
{
DiskFileItemFactory factory = new DiskFileItemFactory(); //实例化工厂对象
ServletFileUpload fileupload = new ServletFileUpload(factory); //实例化工厂解析对象
try {
List<FileItem> list = fileupload.parseRequest(request); //解析工厂
for (FileItem f : list) {
if (!f.isFormField()) { //当为文件字段
String directoryname = this.getServletContext().getRealPath("/upload");
File directory = new File(directoryname);
if(!directory.exists() || !directory.isDirectory()){ //创建上传目录
directory.mkdirs();
}
System.out.println(f.getFieldName()); //file
System.out.println(f.getName()); // xxx.jpg,只有文件名
System.out.println(f.getContentType()); //image/jpeg
InputStream in = f.getInputStream();
ServletOutputStream out = response.getOutputStream();
FileOutputStream file = new FileOutputStream(new File(directoryname, f.getName())); //根据传的文件名命名
int len = 0;
byte[] b = new byte[1024];
while ((len = in.read(b)) != -1) {
out.write(b, 0, len); //输出图像
file.write(b, 0, len); //保存文件
}
file.close(); //关闭文件
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
文件上传路径名问题
可以看到上面传输的文件只会获得文件名,但在一些如IE浏览器中容易出现传输的文件名带路径的问题,如:C:/XXX/xxx.xx
,此时上面的代码就会出错。因此可以使用commons-io
包下FilenameUtils
类提供的静态方法getName()
,其对于不管是否带路径的文件字符串,都只会获取文件名部分的字符串,举例:
System.out.println(FilenameUtils.getName("c:/sda/fdg/s")); //s
System.out.println(FilenameUtils.getName("c:/sda/fdg/s.fs")); //s.fs
System.out.println(FilenameUtils.getName("s.de")); //s.de
System.out.println(FilenameUtils.getName("fdg/s")); //s
System.out.println(FilenameUtils.getName("/sda/fdg/s")); //s
文件上传问题
1.注入问题
对于上面那段代码,会在项目下新建一个upload文件夹,并生成对应文件,此时访问:http://127.0.0.1:8080/项目名/upload/文件名
即可看到刚才上传的文件内容。但是,此时要注意如果上传了像下面这样的文件:
//xxx.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<!DOCTYPE>
<html>
<head>
<title></title>
</head>
<body>
<%
Runtime.getRuntime().exec("notepad");
%>
</body>
</html>
此时,在服务器上可能就会执行该notepad
命令打开记事本,即在服务器上执行了用户上传的脚本文件
解决方式:
(1)将上传的文件放到用户无法访问到的文件路径下,如:WEB-INF
(2)限制上传的文件类型,如jsp、exe、bat等
2.文件覆盖问题
对于在上传保存的路径下原有的文件,如果上传的文件和原有的文件重名,将可能发生原文件被覆盖的问题。
解决方式:
给文件名进行处理,如给文件名加上UUID使得名字唯一等
3.文件夹文件内容过多问题
解决方式:
对文件夹目录等进行分类存储,如按日期/文件类别等
4.文件内容过大
解决方式:
通过ServletFileUpload
下的setFileSizeMax()
方法限制单个文件大小,或者setSizeMax()
设置总文件上传大小,或者通过getSize()
获取文件大小后根据上传的文件大小进行处理,举例:
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileupload = new ServletFileUpload(factory);
fileupload.setSizeMax(1024 * 1024 * 3); //文件大小总和不超过3M
fileupload.setFileSizeMax(1024 * 1024); //单个文件大小不超过1M
try{
...
} catch (FileUploadBase.SizeLimitExceededException e) {
response.getWriter().print("文件总和内容过大!");
} catch (FileUploadBase.FileSizeLimitExceededException e) {
response.getWriter().print("文件内容过大!");
} catch (Exception e) {
e.printStackTrace();
}
5.上传文件为空
解决方式:
判断上传的文件名是否为空
6.产生临时文件
在上传文件过程中往往会产生临时文件,导致磁盘空间不足,或者造成磁盘空间的浪费
解决方式:
(1)使用DiskFileItemFactory
对象的setRepository(File)
方法设置临时文件存储位置,举例:
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File("F:/")); //在F盘下存放临时文件
(2)通过FileItem
下的delete()
方法将临时文件删除,举例:
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload fileupload = new ServletFileUpload(factory);
List<FileItem> list = fileupload.parseRequest(request);
for (FileItem f : list) {
...
f.delete(); //将临时文件删除
}
二、文件下载
在返回头response中添加下面的头信息即可:
response.setHeader("content-disposition", "attachment;filename=文件名" );
response.setHeader("content-type", 文件类型);
举例:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
FileInputStream in = new FileInputStream(new File(this.getServletContext().getRealPath("/img/测试图片.jpg")));
ServletOutputStream out = response.getOutputStream();
int len = 0;
byte[] b = new byte[1024];
response.setHeader("content-disposition", "attachment;filename=" + new String("测试.jpg".getBytes("utf-8"), "iso-8859-1")); //避免文件名乱码
response.setHeader("content-type", "image/jpeg");
while((len = in.read(b)) != -1){
out.write(b, 0, len);
}
}
注:
对于上面的文件类型是手动设置十分不便,因此可以用ServletContext
下的getMimeType(文件名)
方法来根据文件名自动获得文件类型,举例:
response.setContentType(this.getServletContext().getMimeType("xxx.jpg")); //自动设置为image/jpeg