如果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();
}
}