[align=center][size=large][b]Pure JS (3.1):上传下载(利用 HTML5 与 Flash)[/b][/size][/align]
本文主要探讨 JavaScript 文件上传与下载,包括 客户端JS 和 服务器端JS 。
客户端支持 HTML5,Flash 和传统(隐藏)表单上传。
服务器端兼容 HTML5 Streaming 方式上传和 Multipart 方式上传,上传与下载都利用了 JAVA 7 NIO 中的 Files.copy(...) 函数。
其实上传与下载本身并不难,麻烦的是一些细节,比如客户端检查文件大小(怎么绕过浏览器的安全限制?),以及下载文件时中文文件名的乱码问题等。
[align=center][size=medium][b]运行效果[/b][/size][/align]
三种上传方式外观相似,如下图所示:
[align=center][img]http://dl.iteye.com/upload/picture/pic/91553/2c6b7679-6fd6-355e-b400-cf800b45b4d0.png[/img][/align]
直接在前两篇文章中使用的 index.html 中增加了上传按钮。点击上传按钮弹出文件选择框(HTML 或 Flash 方式支持文件多选),选择完成后立即开始上传。上传过程中显示上传进度,期间可以取消;完成后添加到下方的完成列表中,可以移除已经上传完成的文件。
HTML5 和 表单上传的样式通过 CSS 样式进行设置,Flash 按钮需要加载一张图片(如附件中的 pure.button.png),可以在创建组件的参数中设置样式。
同时,对于支持 HTML5 的浏览器,还支持拖拽文件进行上传。
拖拽效果如下:
[align=center][img]http://dl.iteye.com/upload/picture/pic/91555/6417eead-057c-3a44-8b6c-2edea39d72b6.png[/img][/align]
只有支持 HTML5 方式的浏览器才提供拖拽功能,如较新版本的 Chrome,FireFox 等;
Safari 上的拖拽区域不会显示出来(因为没法 drop 到一个 div 上,谁有解决的办法吗?),需要拖拽到上传按钮上,并且同时拖拽多个文件到上传按钮时,也只上传一个文件(这是 Safari 的一个 Bug,有办法解决吗?)。
点击文件名称进行下载:
[align=center][img]http://dl.iteye.com/upload/picture/pic/91557/bafb47cb-4b11-3552-8bec-b9ccc93cdeb6.png[/img][/align]
这里比较麻烦的是中文文件名的编码,不同浏览器需要使用不同的编码,在 Java 中进行文件名编码,实现如下:
[code]
private static String encodeFileName(HttpServletRequest req, String name)
throws UnsupportedEncodingException {
String agent = req.getHeader("USER-AGENT").toLowerCase();
if (agent != null
&& agent.indexOf("firefox") < 0
&& agent.indexOf("safari") < 0) {
return URLEncoder.encode(name, "UTF8");
}
return new String(name.getBytes("UTF-8"), "ISO8859-1");
}
[/code]
在多数浏览器中使用 UTF8 ,而在 firefox 和 safari 中使用 ISO8859-1 。经测试在 IE、Firefox、Chorme、Safari、Opera 上都能正常显示中文文件名(不过都只测试了较新的浏览器,有其他问题的话请告知,谢谢)。
[quote] [b]关于测试文件大小[/b]
这里的大小限制为 1 GB,这是因为本地测试,使用 HTML5 方式上传时,传输速度很快,200 MB 左右的文件才能看到进度条的效果。
不过测试 Flash 方式上传是只要选择 10 MB 左右 的文件就可以了,因为速度慢得多,CPU占用率也很高。
生产环境中,一般将文件大小设置为 10 MB 左右。
文件大小限制可以在 app.js 中修改。
[/quote]
[align=center][size=medium][b]客户端[/b][/size][/align]
根据客户端浏览器的支持情况,优先选择 HTML5 方式上传,其次是 Flash 上传;
对于两者都不支持的浏览器,只能使用隐藏的 iframe 和 form 进行上传了。
优缺点比较如下:
[b]HTML5[/b]: 支持拖拽,多选,客户端检查文件大小,显示上传进度,传输速度快;但 IE, Opera 等浏览器不支持。
[b]FLash[/b]:支持多选,客户端检查文件大小,显示上传进度; 但数据传输格式为 multipart/form-data,传输速度较慢。
[b]传统[/b]: 不支持客户端检查文件大小,不显示上传进度等,传输速度也慢。但所有浏览器均支持。
文件上传的实现包含两个部分: JavaScript 部分和 ActionScript 部分,代码量都不大。
JavaScript 部分的主要代码位于 pure.upload.js,它依赖于 jquery.js (1.6 版本)和 pure.js 。
ActionScript 部分是一个 FlashDevelop 项目,源代码位于 PureUpload.as 。
pure.upload 组件的使用方式(位于 app.js ):
除此之外还有更多的配置项,下一篇文章里我们再详细探讨。
[quote] [b]相关参考[/b]
客户端 JS 和 AS 的实现参考了以下开源项目的源码:
AjaxUpload:[url]http://valums.com/ajax-upload[/url]
SWFUpload: [url]http://swfupload.org[/url]
SWFObject: [url]http://code.google.com/p/swfobject[/url]
Flash 开发工具:
FlashDevelop:[url]http://www.flashdevelop.org[/url]
[/quote]
[align=center][size=medium][b]服务器端[/b][/size][/align]
[b]文件上传[/b]
【HTML5 Streaming 类型的格式】
HTML5 方式上传支持直接将文件内容放在请求的 body 中, 这实际上方便了服务器端的解析。只需要直接通过 Request.getInputStream() 获取输入流就可以了。
【Muilpart Form Data 类型的数据格式】
需要增加一个 Filter,对 Jetty 的 MultiPartFilter 做了一些修改,主要是当文件大小超过限制时不创建临时文件,以及解析文件名时不去除 "/"以免后续解析出错。
Tools 类的 upload 方法提供了供 JS 调用的上传功能的实现,利用了 JAVA 7 NIO 的 Files.copy(...),直接将输入流复制到本地文件。文件上传的 Java部分实现如下:
[b]文件下载[/b]
使用 Files.copy(..),直接将本地文件复制到输出流。Java部分实现如下:
[b]JS 代码[/b]
在服务器端 JS 中,使用importPackage 导入 pure.tools 包,在需要的地方调用 Tools 类的方法进行上传下载。
api.js 中的相关实现如下:
这篇文章就先介绍到这里,下一篇文章里我们再深入探讨其中的实现细节。
欢迎指出 bug 或提出代码重构等方面的建议。
谢谢。
本文主要探讨 JavaScript 文件上传与下载,包括 客户端JS 和 服务器端JS 。
客户端支持 HTML5,Flash 和传统(隐藏)表单上传。
服务器端兼容 HTML5 Streaming 方式上传和 Multipart 方式上传,上传与下载都利用了 JAVA 7 NIO 中的 Files.copy(...) 函数。
其实上传与下载本身并不难,麻烦的是一些细节,比如客户端检查文件大小(怎么绕过浏览器的安全限制?),以及下载文件时中文文件名的乱码问题等。
[align=center][size=medium][b]运行效果[/b][/size][/align]
三种上传方式外观相似,如下图所示:
[align=center][img]http://dl.iteye.com/upload/picture/pic/91553/2c6b7679-6fd6-355e-b400-cf800b45b4d0.png[/img][/align]
直接在前两篇文章中使用的 index.html 中增加了上传按钮。点击上传按钮弹出文件选择框(HTML 或 Flash 方式支持文件多选),选择完成后立即开始上传。上传过程中显示上传进度,期间可以取消;完成后添加到下方的完成列表中,可以移除已经上传完成的文件。
HTML5 和 表单上传的样式通过 CSS 样式进行设置,Flash 按钮需要加载一张图片(如附件中的 pure.button.png),可以在创建组件的参数中设置样式。
同时,对于支持 HTML5 的浏览器,还支持拖拽文件进行上传。
拖拽效果如下:
[align=center][img]http://dl.iteye.com/upload/picture/pic/91555/6417eead-057c-3a44-8b6c-2edea39d72b6.png[/img][/align]
只有支持 HTML5 方式的浏览器才提供拖拽功能,如较新版本的 Chrome,FireFox 等;
Safari 上的拖拽区域不会显示出来(因为没法 drop 到一个 div 上,谁有解决的办法吗?),需要拖拽到上传按钮上,并且同时拖拽多个文件到上传按钮时,也只上传一个文件(这是 Safari 的一个 Bug,有办法解决吗?)。
点击文件名称进行下载:
[align=center][img]http://dl.iteye.com/upload/picture/pic/91557/bafb47cb-4b11-3552-8bec-b9ccc93cdeb6.png[/img][/align]
这里比较麻烦的是中文文件名的编码,不同浏览器需要使用不同的编码,在 Java 中进行文件名编码,实现如下:
[code]
private static String encodeFileName(HttpServletRequest req, String name)
throws UnsupportedEncodingException {
String agent = req.getHeader("USER-AGENT").toLowerCase();
if (agent != null
&& agent.indexOf("firefox") < 0
&& agent.indexOf("safari") < 0) {
return URLEncoder.encode(name, "UTF8");
}
return new String(name.getBytes("UTF-8"), "ISO8859-1");
}
[/code]
在多数浏览器中使用 UTF8 ,而在 firefox 和 safari 中使用 ISO8859-1 。经测试在 IE、Firefox、Chorme、Safari、Opera 上都能正常显示中文文件名(不过都只测试了较新的浏览器,有其他问题的话请告知,谢谢)。
[quote] [b]关于测试文件大小[/b]
这里的大小限制为 1 GB,这是因为本地测试,使用 HTML5 方式上传时,传输速度很快,200 MB 左右的文件才能看到进度条的效果。
不过测试 Flash 方式上传是只要选择 10 MB 左右 的文件就可以了,因为速度慢得多,CPU占用率也很高。
生产环境中,一般将文件大小设置为 10 MB 左右。
文件大小限制可以在 app.js 中修改。
[/quote]
[align=center][size=medium][b]客户端[/b][/size][/align]
根据客户端浏览器的支持情况,优先选择 HTML5 方式上传,其次是 Flash 上传;
对于两者都不支持的浏览器,只能使用隐藏的 iframe 和 form 进行上传了。
优缺点比较如下:
[b]HTML5[/b]: 支持拖拽,多选,客户端检查文件大小,显示上传进度,传输速度快;但 IE, Opera 等浏览器不支持。
[b]FLash[/b]:支持多选,客户端检查文件大小,显示上传进度; 但数据传输格式为 multipart/form-data,传输速度较慢。
[b]传统[/b]: 不支持客户端检查文件大小,不显示上传进度等,传输速度也慢。但所有浏览器均支持。
文件上传的实现包含两个部分: JavaScript 部分和 ActionScript 部分,代码量都不大。
JavaScript 部分的主要代码位于 pure.upload.js,它依赖于 jquery.js (1.6 版本)和 pure.js 。
ActionScript 部分是一个 FlashDevelop 项目,源代码位于 PureUpload.as 。
pure.upload 组件的使用方式(位于 app.js ):
var $uploader = $('#file-uploader');
pure.upload({
element: $uploader[0],
action: 'api',
params: { action: 'upload' },
sizeLimit: 1000 * 1024 * 1024,
onComplete: function(file, data){
// Do more after upload complete ...
}
});
除此之外还有更多的配置项,下一篇文章里我们再详细探讨。
[quote] [b]相关参考[/b]
客户端 JS 和 AS 的实现参考了以下开源项目的源码:
AjaxUpload:[url]http://valums.com/ajax-upload[/url]
SWFUpload: [url]http://swfupload.org[/url]
SWFObject: [url]http://code.google.com/p/swfobject[/url]
Flash 开发工具:
FlashDevelop:[url]http://www.flashdevelop.org[/url]
[/quote]
[align=center][size=medium][b]服务器端[/b][/size][/align]
[b]文件上传[/b]
【HTML5 Streaming 类型的格式】
HTML5 方式上传支持直接将文件内容放在请求的 body 中, 这实际上方便了服务器端的解析。只需要直接通过 Request.getInputStream() 获取输入流就可以了。
【Muilpart Form Data 类型的数据格式】
需要增加一个 Filter,对 Jetty 的 MultiPartFilter 做了一些修改,主要是当文件大小超过限制时不创建临时文件,以及解析文件名时不去除 "/"以免后续解析出错。
Tools 类的 upload 方法提供了供 JS 调用的上传功能的实现,利用了 JAVA 7 NIO 的 Files.copy(...),直接将输入流复制到本地文件。文件上传的 Java部分实现如下:
public static long upload(HttpServletRequest req, String name, String target)
throws IOException {
File dest = new File(target);
if (isMultiPart(req)) {
File source = (File) req.getAttribute(name);
Files.copy(source.toPath(), dest.toPath(), REPLACE_EXISTING);
} else {
InputStream is = req.getInputStream();
Files.copy(is, dest.toPath(), REPLACE_EXISTING);
}
return dest.length();
}
[b]文件下载[/b]
使用 Files.copy(..),直接将本地文件复制到输出流。Java部分实现如下:
public static void download(HttpServletRequest req,
HttpServletResponse res, String name, String source)
throws IOException {
String encoded = encodeFileName(req, name);
String disposition = "attachment; filename=\"" + encoded + '"';
res.setContentType("application/octet-stream; charset=UTF-8");
res.setHeader("Content-Disposition", disposition);
File src = new File(source);
OutputStream os = res.getOutputStream();
Files.copy(src.toPath(), os);
}
[b]JS 代码[/b]
在服务器端 JS 中,使用importPackage 导入 pure.tools 包,在需要的地方调用 Tools 类的方法进行上传下载。
api.js 中的相关实现如下:
importPackage(Packages.purejs.tools);
api = {
// Other codes...
upload: function(params, req, res) {
var size = parseFloat(req.getHeader('Content-Length'));
if (!size || size <= 0 ) {
throw 'Invalid Content-Length';
} else if (size > 1000 * 1024 * 1024) {
throw 'File too large.';
}
var prefix = new Date().getTime() + '-';
var name = prefix + Tools.getFileName(req, 'Filedata');
var target = 'upload/' + name;
var size = Tools.upload(req, 'Filedata', target);
return { name: name, size: size };
},
download: function(params, req, res) {
var name = '' + params.name;
name = name.substr(name.indexOf('-') + 1);
var source = 'upload/' + params.name;
Tools.download(req, res, name, source);
}
};
这篇文章就先介绍到这里,下一篇文章里我们再深入探讨其中的实现细节。
欢迎指出 bug 或提出代码重构等方面的建议。
谢谢。