文件的上传和下载,是非常常见的功能。很多的系统中,或者软件中都经常使用文件的上传和下载。 比如:QQ 头像,就使用了上传;邮箱中也有附件的上传和下载功能;OA 系统中审批有附件材料的上传
method=post
请求multipart/form-data
值input type=file
添加上传的文件enctype=multipart/form-data
表示提交的数据,以多段(每一个表单项一个数据段)的形式进行拼接,然后以二进制流的形式发送给服务器。
upload.jsp:
Servlet:
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("文件上传过来了"); } }
web.xml:
<servlet> <servlet-name>UploadServlet</servlet-name> <servlet-class>com.fox.servlet.UploadServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>UploadServlet</servlet-name> <url-pattern>/uploadServlet</url-pattern> </servlet-mapping>
谷歌浏览器按F12,点击Network查看请求体和请求体:
上面我们只是简单地完成了文件上传的步骤,还没有在Servlet中解析上传的文件
要想完整实现文件上传,我们需要用到commons-fileupload.jar包,而commons-fileupload.jar 需要依赖 commons-io.jar 这个包,所以两个包我们都要引入(可以在Maven仓库搜索下载)。
commons-fileupload.jar 中,我们常用的类和方法有哪些?
public static final boolean isMultipartContent(HttpServletRequest request)
判断当前上传的数据格式是否是多段的格式。public List<FileItem> parseRequest(HttpServletRequest request)
解析上传的数据boolean isFormField()
判断当前这个表单项,是普通的表单项(true);还是上传的文件类型(false)String getFieldName()
获取表单项的 name 属性值String getString()
获取当前表单项的value 属性值。String getName()
获取上传的文件名void write( file )
将上传的文件写到 参数 file 所指向的硬盘位置upload.jsp和web.xml还是和上面一样
Servlet:
import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.util.List; public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //先判断上传的数据是否多段数据(只有是多段的数据,才是文件上传的) if(ServletFileUpload.isMultipartContent(request)){ //创建 FileItemFactory 工厂实现类 FileItemFactory fileItemFactory = new DiskFileItemFactory(); // 创建用于解析上传数据的工具类 ServletFileUpload 类 ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory); try { // 解析上传的数据,得到每一个表单项 FileItem List<FileItem> list = servletFileUpload.parseRequest(request); // 循环判断,每一个表单项,是普通类型,还是上传的文件 for (FileItem fileItem : list) { if(fileItem.isFormField()){ // 普通表单项 System.out.println("表单项的name属性值:"+fileItem.getFieldName()); System.out.println("表单项的value属性值:"+fileItem.getString("UTF-8")); }else{ //上传的文件 System.out.println("表单项的name属性值:"+fileItem.getFieldName()); System.out.println("上传的文件名:"+fileItem.getName()); //将上传的文件写入硬盘 fileItem.write(new File("d://test/"+fileItem.getName())); } } } catch (Exception e) { e.printStackTrace(); } } } }
于是,服务器就收到了上传的文件:
response.getOutputStream()
返回适合在响应中写入二进制数据的Servlet输出流。servletContext.getResourceAsStream(String path)
将位于指定路径的资源作为输入流对象返回。servletContext.getMimeType(String file)
返回指定文件的MIME类型,如果MIME类型未知,则返回null。MIME类型详情response.setContentType(String type)
设置发送到客户端的响应的内容的MIME类型response.setHeader("Content-Disposition", "attachment; fileName=文件名")
这个响应头告诉浏览器,收到的数据是需要下载的。 attachment 表示附件,也就是下载的一个文件;fileName 表示下载的文件名。首先在web目录下新建一个file目录,并提前准备好一张图片
Servlet:
import org.apache.commons.io.IOUtils; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class DownloadServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1、获取要下载的文件名 String downloadFileName="pyy.jpg"; //获取ServletContext对象 ServletContext servletContext = getServletContext(); //2、获取要下载的文件的MIME类型 //斜杠/被服务器解析表示地址为http://ip地址:端口号/工程名/ 映射到代码中来是web目录 String mimeType = servletContext.getMimeType("/file/" + downloadFileName); System.out.println("下载的文件类型:"+mimeType); //3、回传前,通过设置响应头告诉客户端返回的MIME类型 response.setContentType(mimeType); //4、回传前,通过设置响应头告诉客户端收到的数据是用于下载 //Content-Disposition响应头:表示收到的数据怎么处理 //attachment:附件的意思,表示用于下载 //filename:表示指定下载的文件名 response.setHeader("Content-Disposition","attachment; filename="+downloadFileName); //5、读取要下载的文件内容(通过ServletContext对象可以读取) InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName); //获取响应的输出流 OutputStream outputStream = response.getOutputStream(); //6、把下载的文件内容回传给客户端 //copy方法读取输入流中全部的数据,复制给输出流,再输出给客户端 IOUtils.copy(resourceAsStream,outputStream); } }
web.xml:
<servlet> <servlet-name>DownloadServlet</servlet-name> <servlet-class>com.fox.servlet.DownloadServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DownloadServlet</servlet-name> <url-pattern>/downloadServlet</url-pattern> </servlet-mapping>
完成上面的步骤,下载文件是没问题了。但是如果我们要下载的文件的文件名是中文的话,你会发现,下载的文件名不正确,是乱码。
原因是在响应头中,不能包含有中文字符,只能包含 ASCII 码。
那么如何解决中文乱码问题呢?
如果客户端浏览器是 IE 浏览器或者是谷歌浏览器,我们需要使用 URLEncoder 类先对中文名进行 UTF-8 的编码操作。这样,IE 浏览器和谷歌浏览器收到含有编码后的字符串后会以 UTF-8 字符集进行解码显示。
解决方案:
//把中文名进行 UTF-8 编码操作,然后把编码后的字符串设置到响应头中 response.setHeader("Content-Disposition","attachment; filename=" + URLEncoder.encode("中文.jpg", "UTF-8"));
火狐浏览器用的是BASE64编码
import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; public class Base64Test { public static void main(String[] args) throws Exception { String str="今天天气真好"; //创建一个Base64编码器 BASE64Encoder base64Encoder = new BASE64Encoder(); //执行Base64编码操作 String encodeStr = base64Encoder.encode(str.getBytes("UTF-8")); //显示Base64编码后的结果 System.out.println(encodeStr); //创建Base64解码器 BASE64Decoder base64Decoder = new BASE64Decoder(); //执行Base64解码操作 byte[] bytes = base64Decoder.decodeBuffer(encodeStr); //显示Base64解码后的结果 String decodeStr = new String(bytes, "UTF-8"); System.out.println(decodeStr); } }
如果客户端浏览器是火狐浏览器。 那么我们需要对中文名进行 BASE64 的编码操作。
这时候需要把请求头 Content-Disposition: attachment; filename=中文名
改为:Content-Disposition: attachment; filename==?charset?B?xxxxx?=
现在我们对这段内容: =?charset?B?xxxxx?=
进行一下说明:
=?
表示编码内容的开始charset
表示字符集B
表示BASE64编码xxxx
表示文件名BASE64编码后的内容?=
表示编码内容的结束解决方案:
// 使用固定格式进行 BASE64 编码后,设置到响应头中 response.setHeader("Content-Disposition","attachment; fileName=" + "=?UTF-8?B?" + new BASE64Encoder().encode("中文.jpg".getBytes("UTF-8")) + "?=");
import org.apache.commons.io.IOUtils; import sun.misc.BASE64Encoder; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; public class DownloadServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1、获取要下载的文件名 String downloadFileName="pyy.jpg"; //获取ServletContext对象 ServletContext servletContext = getServletContext(); //2、获取要下载的文件的MIME类型 //斜杠/被服务器解析表示地址为http://ip地址:端口号/工程名/ 映射到代码中来是web目录 String mimeType = servletContext.getMimeType("/file/" + downloadFileName); System.out.println("下载的文件类型:"+mimeType); //3、回传前,通过设置响应头告诉客户端返回的MIME类型 response.setContentType(mimeType); //4、回传前,通过设置响应头告诉客户端收到的数据是用于下载 //Content-Disposition响应头:表示收到的数据怎么处理 //attachment:附件的意思,表示用于下载 //filename:表示指定下载的文件名 //响应头User-Agent包含浏览器的信息(是哪个浏览器) String ua = request.getHeader("User-Agent"); // 判断是否是火狐浏览器 if (ua.contains("Firefox")) { // 使用固定格式进行 BASE64 编码后,设置到响应头中 response.setHeader("Content-Disposition","attachment; fileName=" + "=?UTF-8?B?" + new BASE64Encoder().encode("中文.jpg".getBytes("UTF-8")) + "?="); }else { //把中文名进行 UTF-8 编码操作,然后把编码后的字符串设置到响应头中 response.setHeader("Content-Disposition","attachment; filename=" + URLEncoder.encode("中文.jpg", "UTF-8")); } //5、读取要下载的文件内容(通过ServletContext对象可以读取) InputStream resourceAsStream = servletContext.getResourceAsStream("/file/" + downloadFileName); //获取响应的输出流 OutputStream outputStream = response.getOutputStream(); //6、把下载的文件内容回传给客户端 //copy方法读取输入流中全部的数据,复制给输出流,再输出给客户端 IOUtils.copy(resourceAsStream,outputStream); } }