swfupload是很好的一个上传组件. 不过它的代码/例子里面给的是PHP和.net的. 本以为直接用很简单, 结果绕了不少弯路. 总结一下, 抛砖引玉.
搭建了个简单的struts环境, 配置个基本的action, 把swfupload的脚本拷贝到一个jsp里面, 然后将upload_url设置为相应的actoin:
window.onload = function() {
var settings = {
flash_url : "swfupload.swf",
upload_url: "/swfupload/upload.do?state=save"
post_params: {},
file_size_limit : "100 MB"
......
结果不能正常工作. 跟踪了一下, 是能够正常到这个action的. 但是里面的处理有问题. 试着用Apache的commons upload组件, 还是不行. 写了一些测试代码, 发现:
request.getContentType() 返回是对的(二进制流)
request.getContentLength() 也是对的, 基本上是文件的长度
request.getInputStream() 有问题, read() 一下 返回的是 -1, 表明已经到末尾了. 在网上查了一下, Apache的Mail List里面有老外遇到了类似的问题, 原因是Struts在包装Form的时候, 已经对这个Stream进行了操作. 所以这里再操作就出错了.
OK, 那么绕过Struts呢? 直接写一个Servlet来试试. (用拦截器应该也可以, 不过没做实验) 在web.xml里面添加一个Servlet, 让他处理所有的.save请求:
<servlet>
<servlet-name>save</servlet-name>
<servlet-class>
upload.SaveServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>save</servlet-name>
<url-pattern>*.save</url-pattern>
</servlet-mapping>
然后把upload.jsp里面改掉, 使用新的url:
upload_url: "/swfupload/upload.save"
嗯..... Servlet里面该如何处理呢?
为了查看具体的内容, 构造一个 txt 文件, 用来做上传测试. 里面内容都是简单的英文避免乱码.
abcdefg0123456789
abcdefg0123456789
然后, 在Wiredshark里面, 捕捉上传的请求到底是什么东西.
写一些测试代码来看看. 在Servlet里面, 使用下面的测试代码来看看request里面是什么东西:
可以看到, swfobject在上传的时候, 使用的是multipart/formdata类型的请求(和普通上传是一致的). 里面的数据是分块的,每一块可能为:
form-data类型的数据. 里面是swfobject附加的一些补充信息, 比如上传的文件名, 等等;
Application/octet-stream. 里面是实际的文件内容(二进制流).
这样就好办了. 既然有了这些数据, 在Servlet中就能处理.写个测试程序, 看看Servlet中, inputstream是什么东西:
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
int size = request.getContentLength();
byte[] b = new byte[size];
request.getInputStream().read(b);
System.out.println(new String(b));
}
结果为:
------------Ef1ei4ae0ae0gL6cH2cH2Ef1Ij5ei4
Content-Disposition: form-data; name="Filename"
link.txt
------------Ef1ei4ae0ae0gL6cH2cH2Ef1Ij5ei4
Content-Disposition: form-data; name="Filedata"; filename="link.txt"
Content-Type: application/octet-stream
abcdefg0123456789
abcdefg0123456789
abcdefg0123456789
abcdefg0123456789
------------Ef1ei4ae0ae0gL6cH2cH2Ef1Ij5ei4
Content-Disposition: form-data; name="Upload"
Submit Query
------------Ef1ei4ae0ae0gL6cH2cH2Ef1Ij5ei4--
这样,事情就明了了. 上面的数据的含义为:
以 ------------Ef1ei4ae0ae0gL6cH2cH2Ef1Ij5ei4-- 类似的唯一ID分隔(这个ID每个请求都会不同). 同样, 该ID可以从request.getContextType() 中得到.
分隔后, 是 Content-Disposition: form-data; name="xxxxxx" 是swfobject附加的一些信息. 具体哪些信息, 可以从 name 中读取.
如果name="Filedata", 则相当于找到了实际的数据. 继续向下读两行(跳过Content-Type: application/octet-stream 和 一个空行), 开始读入数据, 一直到遇到了下一个分隔ID.
基本思路就是这样, 可以按照这个次序写一段程序来解析request.getInputStream(), 来把实际的数据读取出来.
嗯...自己比较懒, 有没有更好的方法? 当然有, Apache Common Upload 组件其实已经完成了上面的解析的工作. 直接拿过来用, 在上面配置的upload.SaveServlet 中补充代码:
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
//防止中文文件名乱码
upload.setHeaderEncoding( "UTF-8" );
try {
List<FileItem> items = upload.parseRequest(request) ;
if (items != null ) {
Iterator<FileItem> itr = items.iterator();
while (itr.hasNext()) {
FileItem item = (FileItem) itr.next();
if (item.isFormField()) {
continue ;
} else {
File fullFile= new File(item.getName());
//文件都保存在c:\uploadtest文件夹下面, 使用原始的文件名
File savedFile= new File( "c:/uploadtest/" , fullFile.getName());
item.write(savedFile);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
//结束upload. 如果没有这一行, 浏览器里面一直是uploading....
response.getOutputStream().println ( "200 OK" );
}
在IE, FireFox, Opera下测试通过. 其实到最后, 自己写的东西几乎没有, 都是把现成的东西拿来拼一拼~~ 效果如图:
其它的东西, 比如swfupload怎么安装配置, 具体的参数是什么, 就不写了,它自己的文档已经写的很详细了~~
另:我自己是用Filter拦截的,果然好用,但是在指定上传路径的时候,我指定的是相对于站点根目录的路径/xxx/images/id,但是文件却上传到了C:\xxx\images\id路径下,所以只好重新设定上传路径为绝对路径。
1,Jsp中获取当前站点目录的绝对路径:
方法:
request.getRealPath(String str);
已经不建议使用,替代方法是:
request.getSession().getServletContext().getRealPath(String str);
其中参数str可以是文件或者文件夹名称。
2,Jsp经常在windows下开发,在Linux服务器上运行,而两个系统的“文件分隔符”是不一样的,windows下是“\”,linux下是“/”,通用的获取当前系统的“文件分隔符”的方法是:
System.getProperty("file.separator");