Servlet学习记录4
带进度条的文件上传
UploadServlet只实现了普通的文件上传,并附带普通文本域的提交。如果需要显示上传进度条,实时显示上传速度等,需要配合使用Ajax技术。这里仍然使用Apache的commons-fileupload实现文件上传。commons-fileupload从1.2版本开始支持上传监听器,能实时监听上传情况。
工作原理
实时显示上传进度的原理是服务器在处理上传文件的同时,将上传进度的信息例如文件总长度、已上传多少、传输速率等写入Session中。客户端浏览器利用Ajax技术再新开一个独立的线程从Session中获取上传进度信息,并实时显示。Ajax技术能够不刷新页面获取服务器数据。Session可看做是服务器内存,可用于存放少量的客户信息。Session详细用法请参看Session章节。
本代码运行需要commons-fileupload-1.2.1.jar与commons-io-1.4.jar。
上传进度条
上传进度条用两个<div>标签实现,通过控制<div>的css属性就可以显示一个HTML版的进度条<div>标签连同css样式、JavaScript脚本、Ajax技术,可以实现非常丰富的效果,代码为:
<style type="text/css">
body, td, div {font-size: 12px; font-familly: 宋体; }
#progressBar {width: 400px; height: 12px; background: #FFFFFF; border: 1px solid #000000; padding: 1px; }
#progressBarItem {width: 30%; height: 100%; background: #FF0000; }
</style>
<div id="progressBar"><div id="progressBarItem"></div></div>
进度条显示效果如下所示,要控制进度条的显示进度,只需要用JavaScript控制ID为progressBarItem的百分比宽度,比如30%、50%等。

上传监听器
commons-fileupload版本1.2支持上传监听,只需要实现一个监听器,并把它添加到上传组件上即可。监听器需要实现它的ProgressListener接口,如下:
package com.helloweenvsfei.servlet.upload;
import org.apache.commons.fileupload.ProgressListener;
public class UploadListener implements ProgressListener {
private UploadStatus status;
public UploadListener(UploadStatus status) {
this.status = status;
}
public void update(long bytesRead, long contentLength, int items) {
status.setBytesRead(bytesRead);
status.setContentLength(contentLength);
status.setItems(items);
}
}
ProgressListener接口只有一个方法:update(long bytesRead, long contentLength, int items)。参数bytesRead表示已经上传的字节数,contentLength表示上传文件的总长度(如果为-1则表示总长度未知),items表示正在上传第几个文件。
添加了该监听器后,上传组件在上传文件时,会不断地回调该方法,回传这些数据。利用这些数字,就可以计算出文件上传的进度,用进度条实时显示出来。因此需要把这些数据保存起来。代码中把数据保存到了一个UploadStatus中。这是一个普通的Java Bean,代码如下:
package com.helloweenvsfei.servlet.upload;
public class UploadStatus {
private long bytesRead;
private long contentLength;
private int items;
private long startTime = System.currentTimeMillis();
public long getBytesRead() {
return bytesRead;
}
public void setBytesRead(long bytesRead) {
this.bytesRead = bytesRead;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public int getItems() {
return items;
}
public void setItems(int items) {
this.items = items;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
}
UploadStatus中还记录了开始上传的时间,用于计算上传速率、估算上传总时间等。
监听上传进度
处理文件上传的Servlet为ProgressUploadServlet。与前面的UploadServlet类似,仍然使用ServletFileUpload类。监听上传过程需要为ServletFileUpload安装一个监听器,然后把存有上传进度信息的UploadStatus对象放进Session。上传文件使用的是POST方法,因此需要把代码写到doPost()方法中,代码如下:
package com.helloweenvsfei.servlet.upload; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload; public class ProgressUploadServlet extends HttpServlet { private static final long serialVersionUID = -4935921396709035718L; public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { // 上传状态
UploadStatus status = new UploadStatus(); // 监听器
UploadListener listener = new UploadListener(status); // 把 UploadStatus 放到 session 里
request.getSession(true).setAttribute("uploadStatus", status); // Apache 上传工具
ServletFileUpload upload = new ServletFileUpload(
new DiskFileItemFactory()); // 设置 listener
upload.setProgressListener(listener); try {
List itemList = upload.parseRequest(request); for (Iterator it = itemList.iterator(); it.hasNext();) {
FileItem item = (FileItem) it.next();
if (item.isFormField()) {
System.out.println("FormField: " + item.getFieldName()
+ " = " + item.getString());
} else {
System.out.println("File: " + item.getName()); // 统一 Linux 与 windows 的路径分隔符
String fileName = item.getName().replace("/", "\\");
fileName = fileName.substring(fileName.lastIndexOf("\\")); File saved = new File("C:\\upload_test", fileName);
saved.getParentFile().mkdirs(); InputStream ins = item.getInputStream();
OutputStream ous = new FileOutputStream(saved); byte[] tmp = new byte[1024];
int len = -1; while ((len = ins.read(tmp)) != -1) {
ous.write(tmp, 0, len);
} ous.close();
ins.close(); response.getWriter().println("已保存文件:" + saved);
}
}
} catch (Exception e) {
e.printStackTrace();
response.getWriter().println("上传发生错误:" + e.getMessage());
}
}
}
现在使用ProgressUploadServlet来处理文件上传,就能获取到上传开始时间、已上传的字节数、总的字节数、正在上传第几个文件等信息了。只不过这些信息还是保存在Session里,需要再写一个程序读取出来。
读取上传进度
上传进度保存在Session中的uploadStatus属性中,从该属性中获取UploadStatus对象,并将上传进度信息读取出来。上传文件只会使用ProgressUploadServlet的doPost()方法而没有占用doGet()方法,因此这里使用doGet()方法来读取上传进度。也就是说,如果以POST方式访问ProgressUploadServlet,会执行上传代码:如果以GET方式访问ProgressUploadServlet,会执行读取上传进度的代码。
根据上传开始时间、已上传的字节数、总的字节数等原始数据,可以计算出已传输时间、传输速率、传输总时间、剩余时间、已完成百分比等。这些数据最终要被Ajax程序读取、以进度条形式显示,因此最后将它们用分隔符"||"分开,便于程序处理,代码如下:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragrma", "no-cache");
response.setDateHeader("Expires", 0); UploadStatus status = (UploadStatus) request.getSession(true)
.getAttribute("uploadStatus"); if (status == null) {
response.getWriter().println("没有上传信息");
return;
} long startTime = status.getStartTime();
long currentTime = System.currentTimeMillis(); // 已传输的时间 单位:s
long time = (currentTime - startTime) / 1000 + 1; // 传输速度 单位:byte/s
double velocity = ((double) status.getBytesRead()) / (double) time; // 估计总时间 单位:s
double totalTime = status.getContentLength() / velocity; // 估计剩余时间 单位:s
double timeLeft = totalTime - time; // 已完成的百分比
int percent = (int) (100 * (double) status.getBytesRead() / (double) status
.getContentLength()); // 已完成数 单位:M
double length = ((double) status.getBytesRead()) / 1024 / 1024; // 总长度 单位:M
double totalLength = ((double) status.getContentLength()) / 1024 / 1024; // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件
String value = percent + "||" + length + "||" + totalLength + "||"
+ velocity + "||" + time + "||" + totalTime + "||" + timeLeft
+ "||" + status.getItems(); response.getWriter().println(value);
}
现在上传文件使用快捷键Ctrl+N新开一个子窗口访问ProgressUploadServlet,就可以看到文字版的上传进度。不断的刷新,就能不断地更新。不过手工查看上传进度太笨拙了,下一步要写一个Ajax程序,自动获取该Servlet返回的数据、将数据显示到进度条上,并且每一秒钟自动更新一次。
显示上传进度
上传文件时,如果不对表单做特别处理,提交表单后会转到另一个页面,造成页面的刷新。而且新页面显示之前,浏览器会因等待而显示白屏。如果 上传的文件很大,白屏时间会很长。因此需要对表单做一些特别处理,使提交表单后原页面的内容不变同时显示进度条,直到文件上传结束,从而避免出现白屏。方法是更改Form的target属性,代码如下:
<iframe name=upload_iframe width=0 height=0></iframe> <form action="servlet/ProgressUploadServlet" method="post" enctype="multipart/form-data" target="upload_iframe" onsubmit="showStatus(); "> <input type="file" name="file1" style="width: 350px; "> <br />
<input type="file" name="file2" style="width: 350px; "> <br />
<input type="file" name="file3" style="width: 350px; "> <br />
<input type="file" name="file4" style="width: 350px; "> <input type="submit"
value=" 开始上传 " id="btnSubmit"></form>
target属性默认为_self。如果target为默认值,则提交后的新页面会在当前窗口显示,造成当前窗口短暂地白屏。在页面内添加一个隐藏的iframe(通过指定iframe的宽高为0实现隐藏),把target属性指定为该iframe,则提交后的新页面会在iframe内显示,iframe内会暂时白屏。因为iframe是隐藏的,所以上传文件的时候当前页面看不出任何变化。
注意上传文件的表单要指定method="POST"与enctype="multipart/form-data"。另外为表单添加了onsubmit="showStatus()"事件,表单提交时会执行页面内的showStatus()方法。这是一段javaScript程序代码。该方法显示进度条,并用Ajax读取Session里保存的上传进度,实时刷新进度条。showStatus()方法的代码如下:
function showStatus(){
_finished = false;
$('status').style.display = 'block';
$('progressBarItem').style.width = '1%';
$('btnSubmit').disabled = true;
setTimeout("requestStatus()", 1000);
}
Ajax能够不刷新页面改变页面的内容。它的原理是创建一个request,用这个request获取其他页面的内容,并显示到页面上。因为没有在浏览器中输入新的地址,所以页面不会有刷新、拉动等。
Ajax的核心就是这个request,学名叫XMLHttpRequest,需要浏览器支持。目前所有主流的浏览器都能提供XMLHttpRequest。XMLHttpRequest的用法本书后面章节有详细介绍。progressUpload.jsp所有代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!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=UTF-8">
<title>Insert title here</title>
<style type="text/css">
body, td, div {font-size: 12px; font-familly: 宋体; }
#progressBar {width: 400px; height: 12px; background: #FFFFFF; border: 1px solid #000000; padding: 1px; }
#progressBarItem {width: 30%; height: 100%; background: #FF0000; }
</style>
</head> <body> <iframe name=upload_iframe width=0 height=0></iframe> <form action="servlet/ProgressUploadServlet" method="post" enctype="multipart/form-data" target="upload_iframe" onsubmit="showStatus(); "> <input type="file" name="file1" style="width: 350px; "> <br />
<input type="file" name="file2" style="width: 350px; "> <br />
<input type="file" name="file3" style="width: 350px; "> <br />
<input type="file" name="file4" style="width: 350px; "> <input type="submit"
value=" 开始上传 " id="btnSubmit"></form> <div id="status" style="display: none; ">
上传进度条:
<div id="progressBar"><div id="progressBarItem"></div></div>
<div id="statusInfo"></div>
</div> <br/>
<br/>
<br/>
<br/>
<br/> <script type="text/javascript"> var _finished = true; function $(obj){
return document.getElementById(obj);
} function showStatus(){
_finished = false;
$('status').style.display = 'block';
$('progressBarItem').style.width = '1%';
$('btnSubmit').disabled = true; setTimeout("requestStatus()", 1000);
} function requestStatus(){ if(_finished) return; var req = createRequest(); req.open("GET", "servlet/ProgressUploadServlet");
req.onreadystatechange=function(){callback(req);}
req.send(null); setTimeout("requestStatus()", 1000);
} function createRequest()
{
if(window.XMLHttpRequest)//ns
{
return new XMLHttpRequest();
}else//IE
{
try{
return new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
return null;
}
function callback(req){ if(req.readyState == 4) {
if(req.status != 200){
_debug("发生错误。 req.status: " + req.status + "");
return;
} _debug("status.jsp 返回值:" + req.responseText); var ss = req.responseText.split("||"); // 格式:百分比||已完成数(M)||文件总长度(M)||传输速率(K)||已用时间(s)||估计总时间(s)||估计剩余时间(s)||正在上传第几个文件
$('progressBarItem').style.width = '' + ss[0] + '%';
$('statusInfo').innerHTML = '已完成百分比: ' + ss[0] + '% <br />已完成数(M): ' + ss[1] + '<br/>文件总长度(M): ' + ss[2] + '<br/>传输速率(K): ' + ss[3] + '<br/>已用时间(s): ' + ss[4] + '<br/>估计总时间(s): ' + ss[5] + '<br/>估计剩余时间(s): ' + ss[6] + '<br/>正在上传第几个文件: ' + ss[7]; if(ss[1] == ss[2]){
_finished = true;
$('statusInfo').innerHTML += "<br/><br/><br/>上传已完成。";
$('btnSubmit').disabled = false;
}
}
}
function _debug(obj){
var div = document.createElement("DIV");
div.innerHTML = "[debug]: " + obj;
document.body.appendChild(div);
} </script> </body>
</html>
运行效果如下:
文件上传进度条是很人性化的设计,尤其是文件很大的情况。还可以把进度条放到一个可移动的层上,并美化一下,使之看起来更像对话框。
Servlet学习记录4的更多相关文章
- Servlet学习记录3
提交表单信息 Web程序的任务是实现服务器与客户端浏览器之间的信息交互.客户端提交的信息可能来自表单里的文本框,密码框,选择框,单选按钮,复选框以及文件域.这些表单信息被以参数形式提交到了服务器.Se ...
- Servlet学习记录2
读取web.xml参数 上篇文章ImageServlet里只设置了JPG,GIF,DOC类型文件的Content-Type.如果这时候需求变化了,需要增加Excel文件格式的Content-Type, ...
- servlet学习记录:Servlet中的service()方法
Servlet的生存时间是由init,service,destory方法构成,这里分析一下service这个方法 Servlet接口中定义了一个service()方法,而我们一般是使用HttpServ ...
- Servlet学习记录
个人认为servlet属于一种控制程序,可以处理浏览器的请求并做出对应的回应.我们经常使用的是让一个类去继承HttpServlet,然后在doget或者dopost里面写东西. 目前我个人常在doge ...
- 我的Spring学习记录(五)
在我的Spring学习记录(四)中使用了注解的方式对前面三篇做了总结.而这次,使用了用户登录及注册来对于本人前面四篇做一个应用案例,希望通过这个来对于我们的Spring的使用有一定的了解. 1. 程序 ...
- Spring 学习记录8 初识XmlWebApplicationContext(2)
主题 接上文Spring 学习记录7 初识XmlWebApplicationContext refresh方法 refresh方法是定义在父类AbstractApplicationContext中的. ...
- HTTP学习记录
title: HTTP学习记录 toc: true date: 2018-09-21 20:40:48 HTTP协议,HyperText Transfer Protocol,超文本传输协议,是因特网上 ...
- 我的Spring Boot学习记录(二):Tomcat Server以及Spring MVC的上下文问题
Spring Boot版本: 2.0.0.RELEASE 这里需要引入依赖 spring-boot-starter-web 这里有可能有个人的误解,请抱着怀疑态度看. 建议: 感觉自己也会被绕晕,所以 ...
- 学习记录-java基础部分(一)
学习记录-java基础部分(一) 参考:GitHub上的知名项目:javaGuide : https://github.com/Snailclimb/JavaGuide/blob/master/doc ...
随机推荐
- Junit单元测试随笔
Junit单元测试随笔 Mooctest 使用心得: Mooctest IDE非常适合新手使用,相比于Eclipse,他免去了配置环境的麻烦.eclipse中 Junit 编写代码经验总结 刚开始写测 ...
- The Oregon Trail 俄勒冈之旅
发售年份 1971 平台 多平台 开发商 MECC 类型 教育娱乐 https://www.youtube.com/watch?v=QBOLN7I8omY
- vue- 项目之前端页面搭建1
项目分析 首页 导航.登录注册栏.轮播图.地板导航登录注册 选项卡免费课 课程分类.筛选.课程列表免费课详情 课程封面视频.优惠活动倒计时.选项卡我的购物车 全选.商品价格统计购买结算 购买成功 我的 ...
- 注解@Resource和@Autowired区别对比
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Sprin ...
- httpd2.4.6配置文件解释说明
本文httpd版本为:2.4.6 ServerRoot 先来看一下httpd.conf配置文件中的ServerRoot默认定义: # cat /etc/httpd/conf/httpd.conf |e ...
- gradle 打包springboot项目,找不到项目jar application.class
如题:gradle 打包springboot项目,找不到项目jar入口main方法:application.class 检查:lib/目录下没有相应项目的jar包 用gradle命令行查看日志:gra ...
- (转)初学Git及简单搭建git服务器和客户端
终于搞定,mac自己作为git服务器,mac也是客户端,如何免登 从另外一个linux服务器的上传公钥得到提示 ssh-copy-id -i ~/.ssh/id_rsa.pub git@192.168 ...
- CentOS7中KVM虚拟机内存、CPU调整
CentOS7中KVM虚拟机内存.CPU调整 1. 调小虚拟机内存 调小虚拟机内存可以动态实现,不用关机 1.1 查看当前内存大小 [root@kvm01 ~]# virsh dominfo vm1- ...
- Orchard-官方文档翻译1 Orchard的工作方式
开发一个CMS(内容管理系统)程序,与开发一个普通的应用程序很大情况下是不同的,CMS程序更像是一个应用程序的管理器系统.当我们在设计这个系统的时候,第一考虑的是它的扩展性,这是一个非常有挑战的开放式 ...
- java中的线程中断
线程会根据中断标志位 自行了断自己 https://www.cnblogs.com/yangming1996/p/7612653.html 如何停止线程 1.设置中断标识位 2.sleep时设置中断标 ...