renderFile文件下载问题

使用renderFile做文件下载功能。pc上的各浏览器只有在下载一半时取消下载才会报错,这个我知道原因,但是手机上的浏览器打开下载链接还没点确定开始下载就报错了,有点不明白。请问有人知道原因吗?要怎么解决,虽然可以下载,但文件服务器后台错误日志刷个不停,一会儿文件就很大了。

7d74e8e7-4f7f-45c9-bdab-78053d553efd.png

评论区

JFinal

2017-10-18 19:59

在日志里面针对这个异常单独配置,忽略掉这个异常就可以了,假定你的配置前缀为 log4j,大致如下:
log4j.com.jfinal.render.FileRender = OFF

不太记得配置细节了,上面的配置可能是错误的,如果不对,再试试下面这个:
log4j.org.apache.catalina.connector.ClientAbortException = OFF

还有一个更好的办法就是通过继承 RenderFactory 并覆盖掉 getFileRender() 方法,接管这个以后用自己的 MyFileRender extends FileRender 来代替原来的实现

在 MyFileRender 中的大致代码如下:
try {
super.render();
} catch(Exception e) {
if (e.getMessage().contains("ClientAbortException") ) {
// 忽略;
} else {
throw new RuntimeException(e);
}
}

JFinal

2017-10-18 20:00

当然,最后要配置一下:
me.setRenderFactory(new MyRenderFactory());

i++

2017-10-19 18:02

@JFinal
FileRender中的代码:

} finally {
if (inputStream != null)
try {inputStream.close();} catch (IOException e) {LogKit.error(e.getMessage(), e);}
if (outputStream != null)
try {outputStream.close();} catch (IOException e) {LogKit.error(e.getMessage(), e);}
}

186行使用LogKit.error打印了,没有向外面抛,所以在自定义FileRender中无法try做处理。我试过自己实现super.render()功能。但。。。servletContext为null.因为RenderManager中写死的FileRender对象,所以要在自己的FileRender去取servletContext...

i++

2017-10-19 18:08

以下为自定义FileRender代码。只能重写整个类。就为了不打印个异常。建议下个版本把这个异常去掉。


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jfinal.kit.LogKit;
import com.jfinal.kit.PathKit;
import com.jfinal.kit.StrKit;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;
import com.jfinal.render.RenderManager;


/**
* 描述:自定义FileRender.为了不打印org.apache.catalina.connector.ClientAbortException: 远程主机强迫关闭了一个现有的连接。
* @author Robin Zhang
* @created 2017年10月19日 下午6:00:02
*/
public class FileRender extends Render {

private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";

private File file;
private static String baseDownloadPath;
private static ServletContext servletContext;
private String downloadFileName = null;

public FileRender(File file) {
if (file == null) {
throw new IllegalArgumentException("file can not be null.");
}
this.file = file;
init();
}

public FileRender(File file, String downloadFileName) {
this(file);

if (StrKit.isBlank(downloadFileName)) {
throw new IllegalArgumentException("downloadFileName can not be blank.");
}
this.downloadFileName = downloadFileName;
init();
}

public FileRender(String fileName) {
if (StrKit.isBlank(fileName)) {
throw new IllegalArgumentException("fileName can not be blank.");
}

String fullFileName;
fileName = fileName.trim();
if (fileName.startsWith("/") || fileName.startsWith("\\")) {
if (baseDownloadPath.equals("/")) {
fullFileName = fileName;
} else {
fullFileName = baseDownloadPath + fileName;
}
} else {
fullFileName = baseDownloadPath + File.separator + fileName;
}

this.file = new File(fullFileName);
init();
}

public FileRender(String fileName, String downloadFileName) {
this(fileName);

if (StrKit.isBlank(downloadFileName)) {
throw new IllegalArgumentException("downloadFileName can not be blank.");
}
this.downloadFileName = downloadFileName;
init();
}

private void init() {
String downloadPath = RenderManager.me().getConstants().getBaseDownloadPath();
downloadPath = downloadPath.trim();
downloadPath = downloadPath.replaceAll("\\\\", "/");

String baseDownloadPath;
// 如果为绝对路径则直接使用,否则把 downloadPath 参数作为项目根路径的相对路径
if (PathKit.isAbsolutelyPath(downloadPath)) {
baseDownloadPath = downloadPath;
} else {
baseDownloadPath = PathKit.getWebRootPath() + File.separator + downloadPath;
}

// remove "/" postfix
if (baseDownloadPath.equals("/") == false) {
if (baseDownloadPath.endsWith("/")) {
baseDownloadPath = baseDownloadPath.substring(0, baseDownloadPath.length() - 1);
}
}
FileRender.baseDownloadPath = baseDownloadPath;
FileRender.servletContext = RenderManager.me().getServletContext();
}

public void render() {
if (file == null || !file.isFile()) {
RenderManager.me().getRenderFactory().getErrorRender(404).setContext(request, response).render();
return;
}

// ---------
response.setHeader("Accept-Ranges", "bytes");
String fn = downloadFileName == null ? file.getName() : downloadFileName;
response.setHeader("Content-disposition", "attachment; " + encodeFileName(request, fn));
String contentType = servletContext.getMimeType(file.getName());
response.setContentType(contentType != null ? contentType : DEFAULT_CONTENT_TYPE);

// ---------
if (StrKit.isBlank(request.getHeader("Range"))) {
normalRender();
} else {
rangeRender();
}
}

protected String encodeFileName(String fileName) {
try {
// return new String(fileName.getBytes("GBK"), "ISO8859-1");
return new String(fileName.getBytes(getEncoding()), "ISO8859-1");
} catch (UnsupportedEncodingException e) {
return fileName;
}
}

/**
* 依据浏览器判断编码规则
*/
public String encodeFileName(HttpServletRequest request, String fileName) {
String userAgent = request.getHeader("User-Agent");
try {
String encodedFileName = URLEncoder.encode(fileName, "UTF8");
// 如果没有UA,则默认使用IE的方式进行编码
if (userAgent == null) {
return "filename=\"" + encodedFileName + "\"";
}

userAgent = userAgent.toLowerCase();
// IE浏览器,只能采用URLEncoder编码
if (userAgent.indexOf("msie") != -1) {
return "filename=\"" + encodedFileName + "\"";
}

// Opera浏览器只能采用filename*
if (userAgent.indexOf("opera") != -1) {
return "filename*=UTF-8''" + encodedFileName;
}

// Safari浏览器,只能采用ISO编码的中文输出,Chrome浏览器,只能采用MimeUtility编码或ISO编码的中文输出
if (userAgent.indexOf("safari") != -1 || userAgent.indexOf("applewebkit") != -1
|| userAgent.indexOf("chrome") != -1) {
return "filename=\"" + new String(fileName.getBytes("UTF-8"), "ISO8859-1") + "\"";
}

// FireFox浏览器,可以使用MimeUtility或filename*或ISO编码的中文输出
if (userAgent.indexOf("mozilla") != -1) {
return "filename*=UTF-8''" + encodedFileName;
}

return "filename=\"" + encodedFileName + "\"";
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

private void normalRender() {
response.setHeader("Content-Length", String.valueOf(file.length()));
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(file));
outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
for (int len = -1; (len = inputStream.read(buffer)) != -1;) {
outputStream.write(buffer, 0, len);
}
outputStream.flush();
} catch (IOException e) {
if (getDevMode()) {
throw new RenderException(e);
}
} catch (Exception e) {
throw new RenderException(e);
} finally {
if (inputStream != null)
try {
inputStream.close();
} catch (IOException e) {
LogKit.error(e.getMessage(), e);
}
if (outputStream != null)
try {
outputStream.close();
} catch (IOException e) {
if (e.getClass().getName().equals("org.apache.catalina.connector.ClientAbortException")) {
// LogKit.error(e.getMessage());
} else {
LogKit.error(e.getMessage(), e);
}
}
}
}

private void rangeRender() {
Long[] range = { null, null };
processRange(range);

String contentLength = String.valueOf(range[1].longValue() - range[0].longValue() + 1);
response.setHeader("Content-Length", contentLength);
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // status =
// 206

// Content-Range: bytes 0-499/10000
StringBuilder contentRange = new StringBuilder("bytes ").append(String.valueOf(range[0])).append("-")
.append(String.valueOf(range[1])).append("/").append(String.valueOf(file.length()));
response.setHeader("Content-Range", contentRange.toString());

InputStream inputStream = null;
OutputStream outputStream = null;
try {
long start = range[0];
long end = range[1];
inputStream = new BufferedInputStream(new FileInputStream(file));
if (inputStream.skip(start) != start)
throw new RuntimeException("File skip error");
outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
long position = start;
for (int len; position <= end && (len = inputStream.read(buffer)) != -1;) {
if (position + len <= end) {
outputStream.write(buffer, 0, len);
position += len;
} else {
for (int i = 0; i < len && position <= end; i++) {
outputStream.write(buffer[i]);
position++;
}
}
}
outputStream.flush();
} catch (IOException e) {
if (getDevMode())
throw new RenderException(e);
} catch (Exception e) {
throw new RenderException(e);
} finally {
if (inputStream != null)
try {
inputStream.close();
} catch (IOException e) {
LogKit.error(e.getMessage(), e);
}
if (outputStream != null)
try {
outputStream.close();
} catch (IOException e) {
if (e.getClass().getName().equals("org.apache.catalina.connector.ClientAbortException")) {
// LogKit.error(e.getMessage());
} else {
LogKit.error(e.getMessage(), e);
}
}
}
}

/**
* Examples of byte-ranges-specifier values (assuming an entity-body of
* length 10000): The first 500 bytes (byte offsets 0-499, inclusive):
* bytes=0-499 The second 500 bytes (byte offsets 500-999, inclusive):
* bytes=500-999 The final 500 bytes (byte offsets 9500-9999, inclusive):
* bytes=-500 Or bytes=9500-
*/
private void processRange(Long[] range) {
String rangeStr = request.getHeader("Range");
int index = rangeStr.indexOf(',');
if (index != -1)
rangeStr = rangeStr.substring(0, index);
rangeStr = rangeStr.replace("bytes=", "");

String[] arr = rangeStr.split("-", 2);
if (arr.length < 2)
throw new RuntimeException("Range error");

long fileLength = file.length();
for (int i = 0; i < range.length; i++) {
if (StrKit.notBlank(arr[i])) {
range[i] = Long.parseLong(arr[i].trim());
if (range[i] >= fileLength)
range[i] = fileLength - 1;
}
}

// Range format like: 9500-
if (range[0] != null && range[1] == null) {
range[1] = fileLength - 1;
}
// Range format like: -500
else if (range[0] == null && range[1] != null) {
range[0] = fileLength - range[1];
range[1] = fileLength - 1;
}

// check final range
if (range[0] == null || range[1] == null || range[0].longValue() > range[1].longValue())
throw new RuntimeException("Range error");
}
}

JFinal

2017-10-19 18:43

@i++ 下个版本会处理这个需求,感谢你的反馈

zouyinglin

2018-11-24 18:04

renderFile能下载图片服务器上的图片吗 具体怎么实现 菜鸟求解答

热门反馈

扫码入社