Java Web基础知识之文件下载:当你下载文件的时候到底发生了什么?

从网上下载文件几乎是每个人都会遇到的,不管是图片、文本文件还是一些视频,但是我们真的知道在下载的过程中发生了什么吗?本文章就学习一下其中的原理。

关于文件下载存在静态下载和动态下载两种,静态下载是比较容易的,我们平常在网上对很多图片和和视频等的下载有很多其实就是静态下载,那么到底什么是静态下载?静态下载可以理解为对静态资源的下载,静态资源是已经存在于web应用程序的目录或者其子目录中的文件等,不需要通过程序控制进行改变的,可以直接通过浏览器直接进行访问既可以直接下载的,这种可以称之为静态下载;但是并不是左右的资源都是静态资源,有的文件或者图片可能并不保存在应用程序目录下,而可能是在WEB-INF目录中,抑或是在服务器的其他目录中,还可以是在数据库中,另外也可以通过程序控制生成文件,当出现这种情况时就不是静态下载能够解决的了,就要通过编程的手段来发送资源。

一、 静态下载

使用HTML标签<a>来实现静态下载,如下直接请求应用程序目录下的文件:
<body>
	<a href="abc.txt">静态文件下载</a>
</body>
注意如果直接点击的话并不会下载文件而是在浏览器中显示文件的内容,看它的响应头如下:
可以看出content-type是text/plain,这是这个首部决定浏览怎样呈现服务器返回的数据,但是可以通过另存为来完成文件的下载。因为是一个静态文件,我们没办法编程改变它的返回的content-type的值,这些都是servlet容器来决定的,类似如果是png图片则返回的类型是image/png。

二、 动态下载

动态下载就可以通过程序来控制下载的过程以及添加相应的信息。我们首先设置一下JSP文件的下载,因为我们可以通过设置它的content-type来告诉浏览器怎么处理这个数据。

1、 JSP页面下载

要下载的页面如下:
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>JSP File</title>
</head>
<body>
	<%
		response.setContentType("application/x-msdownload");
	%>
	
	Now is <%=new Date(System.currentTimeMillis()) %>
</body>
</html>
还是使用如下HTML标签:
<a href="content.jsp">JSP下载</a>
此时在点击该链接就会弹出save as对话框(在chrome下面测试),但是如果你使用微软的Microsoft Edge浏览器的话则会直接显示该页面内容(Edge另存为也会下载文件)。这里使用的content-type首部的内容是"application/x-msdownload",这是告诉浏览器这是一个要保存到本地的下载的文件,虽然我们告诉了,但是具体浏览器是怎么处理的我们是不知道的,所以对这个首部内容的处理不同的浏览器有不同的处理方式,所以这种办法并不太可靠。
鉴于以上的情况,我们可以使用一个Http首部来完成下载工作,这就是content-disposition,使用这个首部是告诉浏览器不要参与处理,而是要客户自己来处理该文件,在chrome中实验的结果就是也会弹出save as对话框,与使用content-type:application/x-msdownload有相同的效果;但是悲催的是使用微软家的Edge虽然可以下载文件,但是不会像chrome一样弹出对话框,而是直接下载(微软的浏览器更新了为什么还是这么特立独行。。。),如下使用:
<body>
	<%
		response.addHeader("content-disposition", "attachment;filename=hello.jsp");
	%>
	
	Now is <%=new Date(System.currentTimeMillis()) %>
</body>
关于这个首部在使用的时候一定要加上attachment,并且还可以加上返回的文件名,注意这个filename属性和真实的文件名可以不一样,但是返回的文件名是以这个属性的值为准的。还有要提一下,不知道网上有的朋友提到如果使用content-disposition这个首部,必须设置content-type值为"application/x-msdownload",但是根据我的试验并不需要,如果有问题我们可以一起探讨!!
还有第三种办法,那就是使用content-type属性的"application/octet-stream",这个属性值表示是任意的字节流,会强制浏览器打开save as对话框来保存文件,这个在你对mime类型并不了解时可以使用,这样就不用担心每种文件类型的mime类型是什么了。在使用这个属性值的时候,效果和使用content-disposition是一样的,使用如下:
<body>
	<%
		response.setContentType("application/octet-stream");
	%>
	
	Now is <%=new Date(System.currentTimeMillis()) %>
</body>
下面总结一下这三种方式的效果:
  Chrome Microsoft Edge
只使用content-type:application/x-msdownload 打开save as对话框 直接在浏览器显示文件信息
只使用content-disposition 打开save as对话框 下载文件,但不会打开save as对话框
只使用content-type:application/octet-stream 打开save as对话框 直接在浏览器显示文件信息
在此注明:上述的实验的文件都是jsp文件。

2、 编程下载

在这里我们实现一个图片的下载来具体的说明,代码如下:
public class DownloadServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {		
		response.setContentType("application/x-msdownload");
		
		// get file path in the server file system
//		String directory = request.getServletContext().getRealPath("/");
//		System.out.println(directory);
//		BufferedInputStream input = new BufferedInputStream(new FileInputStream(new File(directory, "abc.png")));
		
		InputStream is = request.getServletContext().getResourceAsStream("/abc.png");
		BufferedInputStream input = new BufferedInputStream(is);
		
		OutputStream os = response.getOutputStream();
		byte[] bytes = new byte[input.available()];
		input.read(bytes);
		os.write(bytes);
		input.close();
	}
}
这个servlet的URl是"/downServlet",此处有一个问题要注意,就是在服务器上文件的路径问题,此处开始时使用的是getReal()方法来获取文件在服务器上的绝对路径,这不是一个好方法,因为这个API有一个问题,见如下链接。所以 推荐使用的方法还是使用ServletContext的getResource()来获得相对于web应用程序根目录的资源文件
  • 当只使用content-type:application/x-msdownload时:chrome会打开save as对话框,但是文件名是downServlet而且没有后缀png,即没有识别出文件类型,使用Edge时则是直接显示该图片;
  • 当只使用content-disposition时,如下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {		
	response.addHeader("content-disposition", "attachment; filename=abc.png"); 
	
	InputStream is = request.getServletContext().getResourceAsStream("/abc.png");
	BufferedInputStream input = new BufferedInputStream(is);
	
	OutputStream os = response.getOutputStream();
	byte[] bytes = new byte[input.available()];
	input.read(bytes);
	os.write(bytes);
	input.close();
}
chrome会打开save as对话框,并且文件名就是content-disposition中设置的,并且还识别除了该文件是png类型的图片,在Edge中则会直接下载,没有对话框,就是这么任性。
  • 当只使用content-type:application/octet-stream时,效果和只使用application/x-msdownload一样;
综上所述我们给出比较好的使用办法,必须使用content-disposition来说明文件名和类型;另外最好使用content-type:octet-stream来说明content-type,这样也避免了我们对mime类型陌生的问题,但是如果你熟悉mime类型的话,最好还是指明该类型为好。

猜你喜欢

转载自blog.csdn.net/super_wu1992/article/details/77532857