import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.interceptor.ServletRequestAware;
import org.apache.struts2.interceptor.ServletResponseAware;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.opensymphony.xwork2.ActionSupport;
public class UserAction extends ActionSupport implements ServletRequestAware, ServletResponseAware{
private String paramName;
/**
* request 域
*/
private transient HttpServletRequest request;
/**
* response 域
*/
private transient HttpServletResponse response;
public String getParamName() {
return paramName;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public HttpServletRequest getRequest() {
return request;
}
public HttpServletResponse getResponse() {
return response;
}
/**
* 解析xml文件,dom解析
* @param request
* @param response
* @throws FactoryConfigurationError
*/
private void getXmlFileInfo(HttpServletRequest request,
HttpServletResponse response) throws FactoryConfigurationError
{
// 创建DocumentBuilderFactory实例,指定DocumentBuilder
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try
{
DocumentBuilder db = dbf.newDocumentBuilder();
// 读取文件
Document doc = db.parse(new File(UserAction.class.getResource(
"/menu.xml").toURI()));
// Document doc = db.parse(new File("/menu.xml"));
// 获取子节点
NodeList nl = doc.getElementsByTagName("url");
int len = nl.getLength();
// 获取节点数
for (int i = 0; i < len; i++)
{
Element eltStu = (Element) nl.item(i);
Node eltName = eltStu.getElementsByTagName("name").item(0);
Node eltJspPath = eltStu.getElementsByTagName("path").item(0);
Node eltIsLog = eltStu.getElementsByTagName("islog").item(0);
// 获取各属性的值
String name = eltName.getFirstChild().getNodeValue();
String jspPath = eltJspPath.getFirstChild().getNodeValue();
String isLog = eltIsLog.getFirstChild().getNodeValue();
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
/**
* 获取用户请求IP地址
* @return 用户IP地址
*/
public String getUserRequestIp(HttpServletRequest request)
{
// 本地IP地址
String userIp = "0.0.0.0";
if (request.getHeader("x-forwarded-for") == null)
{
userIp = request.getRemoteAddr();
}
else
{
userIp = request.getHeader("x-forwarded-for");
}
return userIp;
}
/**
* 获取用的的请求数据
* @return
*/
public String getUerRequestInfo()
{
//获取用户请求IP
getUserRequestIp(request);
//页面请求过来的url路径
String urlPath = request.getRequestURI();
//获取最后一个‘/’后面的字符串
String str = urlPath.substring(urlPath.lastIndexOf("/"));
//解析xml文件
getXmlFileInfo(request,response);
//重定向到页面
//response.sendRedirect(url);
return SUCCESS;
}
/**
* 文件下载
* @param filename 要下载的文件名称
* @throws IOException
*/
public String downLoadFile(){
// 下载文件所处于的目录
File dir = new File(ServletActionContext.getServletContext()
.getRealPath("upload"));
File downloadFile = new File(dir, paramName);
try {
downloadFileRanges(downloadFile);
//downloadFileRanges2(downloadFile);
//fileDownload(downloadFile);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 文件下载(支持断点续传)
* @param downloadFile
* @throws IOException
*/
private void downloadFileRanges(File downloadFile) throws IOException {
// 要下载的文件大小
long fileLength = downloadFile.length();
// 已下载的文件大小
long pastLength = 0;
// 是否快车下载,否则为迅雷或其他
boolean isFlashGet = true;
// 用于记录需要下载的结束字节数(迅雷或其他下载)
long lenEnd = 0;
// 用于记录客户端要求下载的数据范围字串
String rangeBytes = request.getHeader("Range");
// 用于随机读取写入文件
RandomAccessFile raf = null;
OutputStream os = null;
OutputStream outPut = null;
byte b[] = new byte[1024];
// 如果客户端下载请求中包含了范围
if (null != rangeBytes)
{
// 返回码 206
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
rangeBytes = request.getHeader("Range").replaceAll("bytes=", "");
// 判断 Range 字串模式
if (rangeBytes.indexOf('-') == rangeBytes.length() - 1)
{
// 无结束字节数,为快车
isFlashGet = true;
rangeBytes = rangeBytes.substring(0, rangeBytes.indexOf('-'));
pastLength = Long.parseLong(rangeBytes.trim());
}
else
{
// 迅雷下载
isFlashGet = false;
String startBytes = rangeBytes.substring(0,
rangeBytes.indexOf('-'));
String endBytes = rangeBytes.substring(
rangeBytes.indexOf('-') + 1, rangeBytes.length());
// 已下载文件段
pastLength = Long.parseLong(startBytes.trim());
// 还需下载的文件字节数(从已下载文件段开始)
lenEnd = Long.parseLong(endBytes);
}
}
// 通知客户端允许断点续传,响应格式为:Accept-Ranges: bytes
response.setHeader("Accept-Ranges", "bytes");
// response.reset();
// 如果为第一次下载,则状态默认为 200,响应格式为: HTTP/1.1 200 ok
if (0 != pastLength)
{
// 内容范围字串
String contentRange = "";
// 响应格式
// Content-Range: bytes [文件块的开始字节]-[文件的总大小 - 1]||[文件的总大小]
if (isFlashGet)
{
contentRange = new StringBuffer("bytes")
.append(new Long(pastLength).toString()).append("-")
.append(new Long(fileLength - 1).toString())
.append("/").append(new Long(fileLength).toString())
.toString();
}
else
{
contentRange = new StringBuffer(rangeBytes).append("/")
.append(new Long(fileLength).toString()).toString();
}
response.setHeader("Content-Range", contentRange);
}
String fileName = getDownloadChineseFileName(paramName);
response.setHeader("Content-Disposition",
"attachment;filename=" + fileName + "");
// 响应的格式是:
response.setContentType("application/octet-stream");
response.addHeader("Content-Length", String.valueOf(fileLength));
try
{
os = response.getOutputStream();
outPut = new BufferedOutputStream(os);
raf = new RandomAccessFile(downloadFile, "r");
// 跳过已下载字节
raf.seek(pastLength);
if (isFlashGet)
{
// 快车等
int n = 0;
while ((n = raf.read(b, 0, 1024)) != -1)
{
outPut.write(b, 0, n);
}
}
else
{
// 迅雷等
while (raf.getFilePointer() < lenEnd)
{
outPut.write(raf.read());
}
}
outPut.flush();
}
catch (IOException e)
{
/**
* 在写数据的时候 对于 ClientAbortException 之类的异常
* 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时, 抛出这个异常,这个是正常的。 尤其是对于迅雷这种吸血的客户端软件。
* 明明已经有一个线程在读取 bytes=1275856879-1275877358,
* 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段, 直到有一个线程读取完毕,迅雷会 KILL
* 掉其他正在下载同一字节段的线程, 强行中止字节读出,造成服务器抛 ClientAbortException。
* 所以,我们忽略这种异常
*/
}
finally
{
if(outPut != null)
{
outPut.close();
}
if(raf != null)
{
raf.close();
}
}
}
/**
* 文件下载(支持断点续传)
* @param file
* @return
*/
private void downloadFileRanges2(File file) {
if (!file.exists())
{
// 文件不存在
return;
}
else
{
FileInputStream fis = null;
OutputStream outPut = null;
try
{
fis = new FileInputStream(file);
response.setHeader("Accept-Ranges", "bytes");
// 下载开始字节
long downBegin = 0;
// 文件大小
long fileSize = 0;
fileSize = file.length();
// 如果是第一次下,还没有断点续传,状态是默认的 200,无需显式设置
// 客户端请求的下载的文件块的开始字节
if (request.getHeader("Range") != null)
{
// 如果是下载文件的范围而不是全部,向客户端声明支持并开始文件块下载
response.setStatus(javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT);
// 从请求中得到开始的字节
downBegin = Long.parseLong(request.getHeader("Range")
.replaceAll("bytes=", "").replaceAll("-", ""));
}
// 下载的文件(或块)长度
response.setHeader("Content-Length", new Long(fileSize
- downBegin).toString());
if (downBegin != 0)
{
// 不是从最开始下载,
response.setHeader("Content-Range", "bytes "
+ new Long(downBegin).toString() + "-"
+ new Long(fileSize - 1).toString() + "/"
+ new Long(fileSize).toString());
}
// 响应的格式是:
response.setContentType("application/octet-stream");
// 为客户端下载指定默认的下载文件名称
String fileName = getDownloadChineseFileName(paramName);
// response.setHeader("Content-Disposition",
// "attachment;filename=" + fileName + "");
response.reset();
response.addHeader("Content-Disposition", "attachment;filename="
+ java.net.URLEncoder.encode(fileName, "utf-8"));
response.addHeader("Content-Length", "" + file.length());
fis.skip(downBegin);
byte[] b = new byte[1024];
int len;
outPut = response.getOutputStream();
while ((len = fis.read(b)) != -1)
{
outPut.write(b, 0, len);
//response.flushBuffer();
}
outPut.flush();
fis.close();
}
catch (FileNotFoundException e)
{
System.out.println("error");
}
catch (IOException e)
{
/**
* 在写数据的时候
* 对于 ClientAbortException 之类的异常
* 是因为客户端取消了下载,而服务器端继续向浏览器写入数据时,
* 抛出这个异常,这个是正常的。
* 尤其是对于迅雷这种吸血的客户端软件。
* 明明已经有一个线程在读取 bytes=1275856879-1275877358,
* 如果短时间内没有读取完毕,迅雷会再启第二个、第三个。。。线程来读取相同的字节段,
* 直到有一个线程读取完毕,迅雷会 KILL 掉其他正在下载同一字节段的线程,
* 强行中止字节读出,造成服务器抛 ClientAbortException。
* 所以,我们忽略这种异常
*/
}
finally
{
if (fis != null)
{
try {
fis.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
if(outPut != null)
{
try
{
outPut.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}
}
/**
* 如果下载文件名为中文,进行字符编码转换
* @param paramName
* @return downloadChineseFileName
*/
private String getDownloadChineseFileName(String paramName)
{
String downloadChineseFileName = "";
try
{
downloadChineseFileName = new String(paramName.getBytes("GBK"),
"ISO8859-1");
}
catch (UnsupportedEncodingException e)
{
e.printStackTrace();
}
return downloadChineseFileName;
}
/**
* 文件下载(不支持断点续传)
* @param file
* @throws IOException
*/
private void fileDownload(File file)
throws IOException
{
// 以流的形式下载文件。
InputStream fis = new BufferedInputStream(new FileInputStream(file));
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
// 清空response
response.reset();
response.addHeader("Content-Disposition", "attachment;filename="
+ java.net.URLEncoder.encode(paramName, "utf-8"));
response.addHeader("Content-Length", "" + file.length());
OutputStream os = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
os.write(buffer);
os.flush();
os.close();
}
@Override
public void setServletResponse(HttpServletResponse arg0) {
this.response = arg0;
}
@Override
public void setServletRequest(HttpServletRequest arg0) {
this.request = arg0;
}
}