Java的文件下载案例
-
需求
- 页面显示超链接
- 点击超链接后弹出下载提示框
- 完成图片文件下载
-
分析:
- 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框(我们的需求是不管什么文件都弹出下载提示框)
- 使用响应头设置资源的打开方式
- content-disposition:attachment;filename = xxx
-
步骤:
- 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
- 定义Servlet
- 获取文件名称
- 使用字节输入流加载文件进内存
- 指定response的响应头:content-disposition:attachment;filename = xxx
- 将数据写出到response输出流
-
问题:
- 中文文件名的问题
- 解决方法:
- 获取客户端使用的浏览器版本信息
- 根据不同的版本信息,设置filename的编码方式不同
-
上代码:
html代码<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="/day15test/downloadServlet?filename=索隆.png">图片</a> </body> </html>
下面是对应的Servlet代码,其中用到了一个工具类DownloadUtils
package test1.download; import test1.util.DownLoadUtils; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.FileInputStream; import java.io.IOException; @WebServlet("/downloadServlet") public class DownloadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1. 获取请求参数(文件名称) String filename = req.getParameter("filename"); //2. 使用字节输入流加载文件进内存 //2.1 找到文件服务器路径 ServletContext servletContext = this.getServletContext(); String realPath = servletContext.getRealPath("/img/" + filename); //2.2 用字节流关联 FileInputStream fis = new FileInputStream(realPath); //3. 设置response响应头 //3.1 设置响应头类型,content-type String mimeType = servletContext.getMimeType(filename); //获取文件MIME类型 resp.setHeader("content-type",mimeType); //3.2 设置响应头打开方式,content-disposition //解决中文文件名问题 //1. 获取user-agent请求头 String agent = req.getHeader("user-agent"); //2.使用工具类方法编码文件名即可 filename = DownLoadUtils.getFileName(agent, filename); resp.setHeader("content-disposition","attachment;filename = " + filename); //4. 将输入流的数据写出到输出流中 ServletOutputStream outputStream = resp.getOutputStream(); byte [] buff = new byte[1024 * 8]; int len = 0; while((len = fis.read(buff)) != -1) { outputStream.write(buff,0,len); } fis.close(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req,resp); } }
下面是DownLoadUtils工具类,这个类中的BASE64Encoder在现在是不建议被使用的,所以不好导包,便从网上下载了一份,代码放在这个类的下面
package test1.util; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; public class DownLoadUtils { public static String getFileName(String agent, String filename) throws UnsupportedEncodingException { if (agent.contains("MSIE")) { //IE浏览器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { BASE64Encoder base64Encoder = new BASE64Encoder(); filename = "?=utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?="; } else { //其它浏览器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; } }
BASE64Encoder类,这个类继承了一个抽象类CharacterEncoder,这个抽象类也需要自己写,放在了这个类的下面(虽然麻烦了点,但是弄出来就不错了)
package test1.util; import java.io.IOException; import java.io.OutputStream; public class BASE64Encoder extends CharacterEncoder { private static final char[] pem_array = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; public BASE64Encoder() { } protected int bytesPerAtom() { return 3; } protected int bytesPerLine() { return 57; } protected void encodeAtom(OutputStream outStream, byte[] data, int offset, int len) throws IOException { byte a; if (len == 1) { a = data[offset]; byte b = 0; byte c = 0; outStream.write(pem_array[a >>> 2 & 63]); outStream.write(pem_array[(a << 4 & 48) + (b >>> 4 & 15)]); outStream.write(61); outStream.write(61); } else { byte b; if (len == 2) { a = data[offset]; b = data[offset + 1]; byte c = 0; outStream.write(pem_array[a >>> 2 & 63]); outStream.write(pem_array[(a << 4 & 48) + (b >>> 4 & 15)]); outStream.write(pem_array[(b << 2 & 60) + (c >>> 6 & 3)]); outStream.write(61); } else { a = data[offset]; b = data[offset + 1]; byte c = data[offset + 2]; outStream.write(pem_array[a >>> 2 & 63]); outStream.write(pem_array[(a << 4 & 48) + (b >>> 4 & 15)]); outStream.write(pem_array[(b << 2 & 60) + (c >>> 6 & 3)]); outStream.write(pem_array[c & 63]); } } } }
CharacterEncoder抽象类
package test1.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.nio.ByteBuffer; public abstract class CharacterEncoder { protected PrintStream pStream; public CharacterEncoder() { } protected abstract int bytesPerAtom(); protected abstract int bytesPerLine(); protected void encodeBufferPrefix(OutputStream aStream) throws IOException { this.pStream = new PrintStream(aStream); } protected void encodeBufferSuffix(OutputStream aStream) throws IOException { } protected void encodeLinePrefix(OutputStream aStream, int aLength) throws IOException { } protected void encodeLineSuffix(OutputStream aStream) throws IOException { this.pStream.println(); } protected abstract void encodeAtom(OutputStream var1, byte[] var2, int var3, int var4) throws IOException; protected int readFully(InputStream in, byte[] buffer) throws IOException { for(int i = 0; i < buffer.length; ++i) { int q = in.read(); if (q == -1) { return i; } buffer[i] = (byte)q; } return buffer.length; } public void encode(InputStream inStream, OutputStream outStream) throws IOException { byte[] tmpbuffer = new byte[this.bytesPerLine()]; this.encodeBufferPrefix(outStream); while(true) { int numBytes = this.readFully(inStream, tmpbuffer); if (numBytes == 0) { break; } this.encodeLinePrefix(outStream, numBytes); for(int j = 0; j < numBytes; j += this.bytesPerAtom()) { if (j + this.bytesPerAtom() <= numBytes) { this.encodeAtom(outStream, tmpbuffer, j, this.bytesPerAtom()); } else { this.encodeAtom(outStream, tmpbuffer, j, numBytes - j); } } if (numBytes < this.bytesPerLine()) { break; } this.encodeLineSuffix(outStream); } this.encodeBufferSuffix(outStream); } public void encode(byte[] aBuffer, OutputStream aStream) throws IOException { ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer); this.encode((InputStream)inStream, aStream); } public String encode(byte[] aBuffer) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer); String retVal = null; try { this.encode((InputStream)inStream, outStream); retVal = outStream.toString("8859_1"); return retVal; } catch (Exception var6) { throw new Error("CharacterEncoder.encode internal error"); } } private byte[] getBytes(ByteBuffer bb) { byte[] buf = null; if (bb.hasArray()) { byte[] tmp = bb.array(); if (tmp.length == bb.capacity() && tmp.length == bb.remaining()) { buf = tmp; bb.position(bb.limit()); } } if (buf == null) { buf = new byte[bb.remaining()]; bb.get(buf); } return buf; } public void encode(ByteBuffer aBuffer, OutputStream aStream) throws IOException { byte[] buf = this.getBytes(aBuffer); this.encode(buf, aStream); } public String encode(ByteBuffer aBuffer) { byte[] buf = this.getBytes(aBuffer); return this.encode(buf); } public void encodeBuffer(InputStream inStream, OutputStream outStream) throws IOException { byte[] tmpbuffer = new byte[this.bytesPerLine()]; this.encodeBufferPrefix(outStream); int numBytes; do { numBytes = this.readFully(inStream, tmpbuffer); if (numBytes == 0) { break; } this.encodeLinePrefix(outStream, numBytes); for(int j = 0; j < numBytes; j += this.bytesPerAtom()) { if (j + this.bytesPerAtom() <= numBytes) { this.encodeAtom(outStream, tmpbuffer, j, this.bytesPerAtom()); } else { this.encodeAtom(outStream, tmpbuffer, j, numBytes - j); } } this.encodeLineSuffix(outStream); } while(numBytes >= this.bytesPerLine()); this.encodeBufferSuffix(outStream); } public void encodeBuffer(byte[] aBuffer, OutputStream aStream) throws IOException { ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer); this.encodeBuffer((InputStream)inStream, aStream); } public String encodeBuffer(byte[] aBuffer) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer); try { this.encodeBuffer((InputStream)inStream, outStream); } catch (Exception var5) { throw new Error("CharacterEncoder.encodeBuffer internal error"); } return outStream.toString(); } public void encodeBuffer(ByteBuffer aBuffer, OutputStream aStream) throws IOException { byte[] buf = this.getBytes(aBuffer); this.encodeBuffer(buf, aStream); } public String encodeBuffer(ByteBuffer aBuffer) { byte[] buf = this.getBytes(aBuffer); return this.encodeBuffer(buf); } }
到此,文件下载和其中的中文乱码问题就解决了。
=====================================================
追加,弄了那么长时间,突然发现一个简单的方法,DownLoadUtils类中导包可以这样导,import java.util.Base64.Encoder;
将DownLoadUtils类改为下面的这个,那么上面从DownLoadUtils类向下的其它类都不需要写了:
package test1.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.Base64.Encoder;
public class DownLoadUtils {
public static String getFileName(String agent, String filename) throws UnsupportedEncodingException {
if (agent.contains("MSIE")) {
//IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
Encoder base64Encoder = Base64.getEncoder();
filename = "?=utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?=";
} else {
//其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}
因为BASE64Encoder不是很安全,所以不建议被使用。