如果Web服务器需要频繁传送文件给客户端时,大多数web容器会提供异步发送的支持,像IIS中的通过ServerSupportFunction(),Apache里面apr_sendfile(),Tomcat6.X通过设置Servlet Request的请求属性等等。。。都可以做到异步发送文件,从而提高服务器性能。
Jetty6.X默认也不提供相关支持,本文提供一种hack方法,仅供参考:
先看测试Servlet:
package com.yovn.labs.testweb;
import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.yovn.labs.sendfile.JettySendFile;
import com.yovn.labs.sendfile.NormalSendFile;
import com.yovn.labs.sendfile.SendFile;
/**
* @author yovn
*
*/
public class SendFileServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
try {
File f=new File("D:\\workspace\\TEST.rar");//about 45M
String action=req.getParameter("action");
SendFile sf=null;
if("1".equals(action))
{
sf=new JettySendFile();
}
else
{
sf=new NormalSendFile();
}
long start=System.currentTimeMillis();
sf.doSend(req, res, f, null);
System.out.println(" service ok, action="+action+",use:"+(System.currentTimeMillis()-start)+"!!!");
} catch (Exception e) {
throw new ServletException(e);
}
}
}
代码很简单明了,action=1时使用Jetty的异步发送,action=2时使用正常方式。
下面是通过firefox直输入地址回车后,下载文件,后台的程序运行结果:
service ok, action=1,use:62!!!
service ok, action=2,use:10688!!!
service ok, action=2,use:9063!!!
service ok, action=1,use:47!!!
当运行1时,实际上客户端还没有下完文件,但是该段代码已经执行完了,IO的操作是异步的。
当运行2时,客户端下完代码才执行完。
以下是Jetty异步发送的代码:
package com.yovn.labs.sendfile;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mortbay.io.EndPoint;
import org.mortbay.io.nio.NIOBuffer;
/**
* @author yovn
*
*/
public class JettySendFile implements SendFile {
static Field endpointField=null;
static Class reqCls=null;
static
{
try {
reqCls=Class.forName("org.mortbay.jetty.Request");
endpointField=reqCls.getDeclaredField("_endp");
endpointField.setAccessible(true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void doSend(HttpServletRequest req, HttpServletResponse res,File todo,String headerOpt)
throws IOException {
if(endpointField==null)throw new IOException("Jetty Not Available!!");
if(!req.getClass().equals(reqCls))
{
throw new IOException("Not in Jetty Context!!");
}
EndPoint ep;
try {
ep = (EndPoint)endpointField.get(req);
if(headerOpt==null)
{
headerOpt="HTTP/1.1 200 OK \r\nContent-Type: APPLICATION/OCTET-STREAM\r\n"+
"Content-Disposition: attachment;filename=\""+todo.getName()+"\"\r\n"+
"Content-Length: "+todo.length()+"\r\n\r\n";
}
byte[] headerBytes=headerOpt.getBytes("UTF-8");
NIOBuffer header=new NIOBuffer(headerBytes.length,false);
header.put(headerBytes);
NIOBuffer buffer=new NIOBuffer(todo);
ep.flush(header, buffer, null);
} catch (IllegalArgumentException e) {
throw new IOException(e);
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}
}
正常发送文件的代码:
package com.yovn.labs.sendfile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author yovn
*
*/
public class NormalSendFile implements SendFile {
/* (non-Javadoc)
* We simply ignore the 'headerOpt'
* @see com.yovn.labs.sendfile.SendFile#doSend(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.io.File, java.lang.String)
*/
public void doSend(HttpServletRequest req, HttpServletResponse res,
File todo, String headerOpt) throws IOException {
res.setHeader("Content-Type", "APPLICATION/OCTET-STREAM");
res.setHeader("Content-Disposition", "attachment;filename=\""+todo.getName()+"\"");
res.setHeader("Content-Length", todo.length()+"");
res.setStatus(HttpServletResponse.SC_OK);
OutputStream out=res.getOutputStream();
InputStream in=new FileInputStream(todo);
byte[] buffer=new byte[8192];
int read=0;
while((read=in.read(buffer))>0)
{
out.write(buffer, 0, read);
}
out.flush();
in.close();
}
}