本实现参考了 Daniel Matuschek - A FilterInputStream with a limited bandwith
分别为设置下载限制速度为1MB/s 128KB/s 256KB/s,使用wget测试下载速度
测试结果如图:
代码如下:
package com.bimwinner.commons.web.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.WriteListener;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
/**
* Simple QoS limit filter
* <b>Note: </b>
* input limit works only when <code>request.getInputStream()</code> was invoked;
* (e.g. form-data upload don't, binary upload do)<br/>
* output limit works only when <code>response.getOutputStream()</code> was invoked;
* (e.g. jsp html don't, servlet download do)
* @author Fuwei Chin
*/
@WebFilter(filterName="QoS",initParams={
@WebInitParam(name="outputLimit",value="1*1024*1024")
})
public class QoSFilter implements Filter {
private int inputLimit=-1;
private int outputLimit=-1;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Double inputLimit=eval(filterConfig.getInitParameter("inputLimit"));
if(inputLimit!=null){
this.inputLimit=inputLimit.intValue();
}
Double outputLimit=eval(filterConfig.getInitParameter("outputLimit"));
if(outputLimit!=null){
this.outputLimit=outputLimit.intValue();
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(inputLimit<=0?req:new QoSServletRequestWrapper(req,inputLimit),
outputLimit<=0?res:new QoSServletResponseWrapper(res,outputLimit));
}
@Override
public void destroy() {
}
private static Double eval(String expr){
if(expr==null){
return null;
}
List<Double> values=new ArrayList<>();
StringTokenizer st=new StringTokenizer(expr, "*");
while(st.hasMoreTokens()){
values.add(Double.parseDouble(st.nextToken()));
}
if(values.size()>0){
double result=1d;
for(Double i:values){
result*=i;
}
return result;
}
return null;
}
}
class QoSServletRequestWrapper extends ServletRequestWrapper {
private int limit;
private ServletInputStream in;
public QoSServletRequestWrapper(ServletRequest request,int limit) {
super(request);
this.limit=limit;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if(in==null){
in=new QosServletInputStream(super.getInputStream(), this.limit);
}
return in;
}
}
class QoSServletResponseWrapper extends ServletResponseWrapper {
private int limit;
private ServletOutputStream out;
public QoSServletResponseWrapper(ServletResponse response,int limit) {
super(response);
this.limit=limit;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if(out==null){
out=new QoSServletOutputStream(super.getOutputStream(), this.limit);
}
return out;
}
}
class QosServletInputStream extends ServletInputStream {
private ServletInputStream in;
private int limit;
private int read;
private long startTime;
public QosServletInputStream(ServletInputStream in,int limit) {
this.in=in;
this.limit=limit;
}
@Override
public int read() throws IOException {
int r=in.read();
if(r!=-1&&(this.read+=1)>limit){
checkSpeed();
}
return r;
}
@Override
public int read(byte[] b) throws IOException {
return this.read(b,0,b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int r=in.read(b, off, len);
if(r!=-1&&(this.read+=r)>limit){
checkSpeed();
}
return r;
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
in.reset();
}
@Override
public boolean markSupported() {
return in.markSupported();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public boolean isFinished() {
return in.isFinished();
}
@Override
public boolean isReady() {
return in.isReady();
}
@Override
public void setReadListener(ReadListener listener) {
in.setReadListener(listener);
}
private void checkSpeed(){
long elapsedTime=System.currentTimeMillis()-startTime;
long sleepTime=1000-elapsedTime;
if(sleepTime>0){
try {
System.out.println("sleep "+sleepTime);
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
this.read=0;
this.startTime=System.currentTimeMillis();
}
}
class QoSServletOutputStream extends ServletOutputStream {
private ServletOutputStream out;
private int limit;
private int written;
private long startTime;
public QoSServletOutputStream(ServletOutputStream out,int limit) {
this.out=out;
this.limit=limit;
}
@Override
public void write(int b) throws IOException {
out.write(b);
if((this.written+=1)>limit){
checkSpeed();
}
}
@Override
public void write(byte[] b) throws IOException {
this.write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
if((this.written+=len)>limit){
checkSpeed();
}
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
out.close();
}
@Override
public boolean isReady() {
return out.isReady();
}
@Override
public void setWriteListener(WriteListener listener) {
out.setWriteListener(listener);
}
private void checkSpeed(){
long elapsedTime=System.currentTimeMillis()-startTime;
long sleepTime=1000-elapsedTime;
if(sleepTime>0){
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
this.written=0;
this.startTime=System.currentTimeMillis();
}
}