文件上传与下载 解密

一、文件上传原理:

1、文件上传的前提:

表单的method必须是post方式。

表单的enctype必须是multipart/form-data

表示的File类型的input,必须有name属性,值不重要可以随便写。

2、如果表单的enctype的值是multipart/form-data,那么传统的获取请求参数的方法失效。

3、正文内容是使用MIME协议进行描述的。

关于MIME协议可到RFC中查找相关的资源,这里简单谈谈个人理解。通过将页面请求传给Servlet处理,Servlet中通过request.getInputStream获取输入流打印出来。

jsp页面如下:

<formaction="${pageContext.request.contextPath}/servlet/Demo1Servlet"method="post"enctype="multipart/form-data">

用户名::<inputtype="text"name="username"><br/>

文件1::<inputtype="file"name="file1"><br/>

文件2::<inputtype="file"name="file2"><br/>

<inputtype="submit"value="提交"/>

</form>

Servlet处理代码如下:

InputStreamin=request.getInputStream();

intlen=-1;

byte[]b=newbyte[1024];

while((len=in.read(b))!=-1){

System.out.print(newString(b,0,len));

}

结果发现打印如下,也就是MIME的格式:

------WebKitFormBoundaryyBXT4NAF1dkh993S

Content-Disposition:form-data;name="username"

chen

------WebKitFormBoundaryyBXT4NAF1dkh993S

Content-Disposition:form-data;name="file1";filename="a.txt"

Content-Type:text/plain

aaaa

------WebKitFormBoundaryyBXT4NAF1dkh993S

Content-Disposition:form-data;name="file2";filename="b.txt"

Content-Type:text/plain

bbbb

------WebKitFormBoundaryyBXT4NAF1dkh993S--

4.MIME协议总结如下:

1>多个输入域,不管是File还是text等等都是以------WebKitFormBoundary连接16位随机Base64位编码的字符串进行隔开的。多个输入域之间的分隔字符串是一致的都是------WebKit...。

2>多个输入域,每个输入域都有自己单独的请求头与请求体。各个请求头与请求体都被------WebKit...字符串分隔

3>输入域请求头格式:

Content-Dispostion:form-data;空格name=”xxx”;filename=”xx”

Content-Type:xxxx(若输入域不是File,则请求头截止到name就没了只有一行。而File类型有filenameContent-Type,整个请求头有二行。)

4>输入域请求体格式:

注意请求头与请求体之间有一行空行的(这点与HTTP协议是一样滴)。请求体中为输入域的内容。若输入域是非File类型,则请求体直接是输入的文本内容。若输入域是File类型,则请求体是File的二进制流信息。

通过对上面的了解,若需要文件上传则必须将表单enctype指定为multipart/form-data,而在服务端获取请求数据,必须遵循MIME协议从InputStream输入流中按MIME协议格式解析数据。手动来解析还是比较麻烦的。所以有Apache提供了优秀的common-fileupload组件。

二、Apachecommons-fileupload

需要依赖两个jar包:commons-fileupload.jarcommons-io.jar

主要有以三个核心类:

DiskFileItemFactory、ServletFileupLoadFileItem三种之间的关系如下:


1>DiskFileItemFactory是创建FileItem对象的工厂,常用方法有:

publicvoidsetSizeThreshold(intsizeThreshold)

设置内存缓冲区的大小,默认值为10K。当上传文件大于缓冲区大小时,fileupload组件将使用临时文件缓存上传文件。

publicvoidsetRepository(java.io.Filerepository)

指定临时文件目录,默认值为System.getProperty("java.io.tmpdir").

publicDiskFileItemFactory(intsizeThreshold,java.io.Filerepository)构造函数,也可以直接newDiskFileItemFactory()采用默认的缓存区大小与临时文件目录。

2>ServletFileUpload负责处理上传的文件数据,并将表单中每个输入项封装成一个FileItem对象中。常用方法有:

booleanisMultipartContent(HttpServletRequestrequest)

判断上传表单是否为multipart/form-data类型

ListparseRequest(HttpServletRequestrequest)

解析request对象,并把表单中的每一个输入项包装成一个fileItem对象,并返回一个保存了所有FileItemlist集合。

setFileSizeMax(longfileSizeMax)//设置单个上传文件的最大值

setSizeMax(longsizeMax)//设置上传文件总量的最大值

setHeaderEncoding(java.lang.Stringencoding)//设置编码格式

setProgressListener(ProgressListenerpListener)//提供可注册监听器来监听上传的进度

3>FileItem封装每个输入域中的数据,包括请求头与请求体。请求体是通过getInputStream得到。请求头分别被封装成getFieldNamegetStringgetNamegetContentType相应的属性。因为常用的方法如下:

booleanisFormField()//判断此FileItem是否普通的输入域(File类型的输入域)

StringgetFieldName//获取此普通输入域的name中的值,即字段名。

StringgetString()/获取本地默认字符编码下的,普通输入域的值,即字段值。

StringgetString(chartSet)

//获取指定字符编码下的普通输入域的值,如getString(utf-8)。

StringgetContentType()//普通文件的MIME类型如text/plain纯文本。

InputStreamgetInputStream()//获取请求体内容。FileItemFile类型时则是文件内容的二进流数据,有这个流那么可以构造输入出存入到服务器中即就是文件上传了。

voiddelete()

//删除临时文件。可以在FileItem的输入流in.close时再调用delete方法

文件上传示例:

publicclassUploadServltextendsHttpServlet{

publicvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)

throwsServletException,IOException{

DiskFileItemFactoryfactory=newDiskFileItemFactory();//1.构造DiskFileItemFactory对象

StringtempFile=getServletContext().getRealPath("/WEB-INF/temp");

FiletempF=newFile(tempFile);

factory.setRepository(tempF);//设置临时文件

//factory.setSizeThreshold(100*1024);//启用临时文件的大小,超过100k,用临时文件。否则直接写到upload中。

ServletFileUploadupload=newServletFileUpload(factory);//2.获取ServletFileUpload对象。

registListener(upload);//设置文件上传进度的监听器来监听上传进度...

if(!upload.isMultipartContent(request)){

thrownewExceptionInInitializerError("表单类型有错");

}

try{

/*upload.setFileSizeMax(1*1024*1024);*///设置单个文件是1M左右大小

/*upload.setSizeMax(1*1024*1024);*///一次上传所有文件总大小不能超过1M

List<FileItem>items=upload.parseRequest(request);//3.获取输入域封装对象FileItem。

for(FileItemitem:items){

if(item.isFormField()){

Stringname=item.getFieldName();

StringparaValue=item.getString("utf-8");

//System.out.println("普通字段有::"+name+"="+paraValue);

}else{//剩下的都是文件类型

StringcontentType=item.getContentType();

/*if(!contentType.startsWith("image/")){

thrownewFileTypeException("文件格式不对,只能上传图片文件");

}*/

StringfileName=item.getName();//获取文件名获取不得路径名的,直接是文件名如a.jgp

System.out.println(fileName);

InputStreamin=item.getInputStream();

fileName=fileName.substring(fileName.lastIndexOf("\\")+1);

handUpload(request,response,factory,upload,in,fileName,item);

}

}

request.setAttribute("message","上传成功了");

request.getRequestDispatcher("/message.jsp").forward(request,response);

}catch(FileUploadBase.FileSizeLimitExceededExceptione){

request.setAttribute("message","上传失败了,文件大小不能超过"+upload.getFileSizeMax());

request.getRequestDispatcher("/message.jsp").forward(request,response);

}catch(FileUploadBase.SizeLimitExceededExceptione){

request.setAttribute("message","上传失败了,总文件大小不能超过"+upload.getSizeMax());

request.getRequestDispatcher("/message.jsp").forward(request,response);

}

/*catch(FileTypeExceptione){

request.setAttribute("message",e.getMessage());

request.getRequestDispatcher("/message.jsp").forward(request,response);

}

*/

catch(Exceptione){

e.printStackTrace();

}

}

privatevoidhandUpload(HttpServletRequestrequest,

HttpServletResponseresponse,DiskFileItemFactoryfactory,

ServletFileUploadupload,InputStreamin,StringfileName,FileItemitem)throwsServletException,IOException{

StringrealPath=getServletContext().getRealPath("/WEB-INF/upload");

/*System.out.println(realPath);*/

StringnewFileName=UUID.randomUUID().toString()+"_"+fileName;

StringinPath=PathHashUtils.getPathFromHash(newFileName);

Filefile=newFile(realPath+"/"+inPath);

if(!file.exists()){

file.mkdirs();

}

byte[]buf=newbyte[1024];

intlen=-1;

BufferedInputStreambin=newBufferedInputStream(in);

BufferedOutputStreambout=newBufferedOutputStream(newFileOutputStream(newFile(file,newFileName)));

while((len=bin.read(buf))!=-1){

bout.write(buf,0,len);

bout.flush();

}

bin.close();

bout.close();

item.delete();//item删除掉,若DiskFileItemFactory中设置了临时文件,则可以将临时文件删除掉。

}

publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)

throwsServletException,IOException{

doGet(request,response);

}

}

文件上传的几个注意事项:

1.中文乱码问题

a、普通输入域请求参数的中文乱码问题

FileItem.getString("UTF-8");

b、上传的文件中文名称问题

方式一:request.setCharacterEncoding("UTF-8");

方式二:ServletFileUpload.setHeaderEncoding("UTF-8");

2、如何保证服务器的安全

上传的文件存放地址不能让用户直接访问到。放到WEB-INF目录下

3、多次上传同名文件的覆盖

如若上传的文件名是:a.txt

方式一:123193219321_a.txt当前时间的毫秒值。(乐观者)

方式二:UUID_a.txt

4、如何防止同一目录下文件太多的问题

方式一:当前的日期作为一个目录。当天上传的文件放到当前的日期目录中。(乐观者)

方式二:hashCode算法,打散存储文件。将文件名如UUID_a.txt,计算出此文件名字符串的低4hashCode值,再计算字符串的第二个4位的hashCode值用16进制字符获取计算结果。如结果为:12/8

如:/WEB-INF/upload/12/8/a.txt

5、上传文件的大小控制(单个文件和总大小)及友好的提示用户

a、单个文件大小控制

ServletFileUpload类的setFileSizeMax(long?fileSizeMax)方法来控制,

超过大小,通过catch出此FileUploadBase.FileSizeLimitExceededException类异常来进行处理。

b、总文件大小控制

ServletFileUpload类的setSizeMax(long?sizeMax)方法来控制,

超过大小,通过catch出此FileUploadBase.SizeLimitExceededException类异常来进行处理。

6、超出10k的文件的临时文件的处理

组件不会自动删除临时文件。

FileItem.delete()手工删除临时文件。必须关流之后再删除。

7、限制上传文件的类型

若只允许jpg文件

方法一:过滤文件名的后缀名。(乐观者)

方法二:FileItem.getContentType得到MIME类型,进一步的过滤

8、监听文件的上传进度

注册监听器:

ServletFileUpload.setProgressListener(newProgressListener())

9、用户没有选择文件上传时的问题

通过FileItem.getName().length()长度来判断,用户在前面应该选择的文件域是否选择了文件。若length<1,则没有选择文件。

三、文件下载

其实就是通过获取文件名后,构造InputStream。然后读取InputStream,将流中的数据写入到Servlet.getOutputStream中就可以了。注意文件名中文乱码及设置response.setHeader(“content-disposition”,”attachment;filename=x”);见“那么年web乱码解决终极办法”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值