在僵尸网络时代,你可以花几百美元租用并运行你自己的分布式拒绝服务攻击,拥有紧急开关可以选择性地关闭昂贵的功能或感激地降低性能是一个巨大的胜利。当您缓解问题时,您的应用程序仍在运行。当然,这种安全措施在高峰或营业时间也很有价值。适用于下载服务器的此类机制之一是动态限制下载速度。为了防止分布式拒绝服务攻击和过高的云发票,请考虑内置的下载限制,您可以在运行时启用和微调。这个想法是限制最大下载速度,全局或每个客户端(IP?连接?Cookie?用户代理?)。
我必须承认,我喜欢
java.io
设计,它有很多简单的
Input
/
OutputStream
和
Reader
/
Writer
实现,每个实现只有一个职责。你想要缓冲? GZIPing?字符编码?文件系统编写?只需编写始终相互协作的所需类即可。好吧,它仍然是阻塞的,但它是在反应型潮人诞生之前设计的。没关系,
java.io
也遵循
开闭原则
:可以简单地增强现有的 I/O 代码而无需触及内置类 - 但通过插入新的装饰器。所以我为
InputStream
创建了一个简单的装饰器,它减慢了我们这边的读取资源,以强制执行给定的下载速度。我正在使用
我最喜欢的
RateLimiter
类
:
public class ThrottlingInputStream extends InputStream {
private final InputStream target;
private final RateLimiter maxBytesPerSecond;
public ThrottlingInputStream(InputStream target, RateLimiter maxBytesPerSecond) {
this.target = target;
this.maxBytesPerSecond = maxBytesPerSecond;
}
@Override
public int read() throws IOException {
maxBytesPerSecond.acquire(1);
return target.read();
}
@Override
public int read(byte[] b) throws IOException {
maxBytesPerSecond.acquire(b.length);
return target.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
maxBytesPerSecond.acquire(len);
return target.read(b, off, len);
}
//less important below...
@Override
public long skip(long n) throws IOException {
return target.skip(n);
}
@Override
public int available() throws IOException {
return target.available();
}
@Override
public synchronized void mark(int readlimit) {
target.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
target.reset();
}
@Override
public boolean markSupported() {
return target.markSupported();
}
@Override
public void close() throws IOException {
target.close();
}
}
任意
InputStream
可以用
ThrottlingInputStream
包装,这样读取实际上会变慢。您可以为每个
ThrottlingInputStream
创建新的
RateLimiter
,也可以创建一个全局的,由所有下载共享。当然,有人可能会争辩说简单的
sleep()
(
RateLimiter
在下面做了什么)会浪费大量资源,但让我们保持这个示例简单并避免非阻塞 I/O。现在我们可以轻松地插入装饰器:
public class ThrottlingInputStream extends InputStream {
private final InputStream target;
private final RateLimiter maxBytesPerSecond;
public ThrottlingInputStream(InputStream target, RateLimiter maxBytesPerSecond) {
this.target = target;
this.maxBytesPerSecond = maxBytesPerSecond;
}
@Override
public int read() throws IOException {
maxBytesPerSecond.acquire(1);
return target.read();
}
@Override
public int read(byte[] b) throws IOException {
maxBytesPerSecond.acquire(b.length);
return target.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
maxBytesPerSecond.acquire(len);
return target.read(b, off, len);
}
//less important below...
@Override
public long skip(long n) throws IOException {
return target.skip(n);
}
@Override
public int available() throws IOException {
return target.available();
}
@Override
public synchronized void mark(int readlimit) {
target.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
target.reset();
}
@Override
public boolean markSupported() {
return target.markSupported();
}
@Override
public void close() throws IOException {
target.close();
}
}
上面的示例将下载速度限制为 64 KiB/s - 显然在现实生活中您希望可以配置这样的数字,最好是在运行时。顺便说一句,我们已经讨论过
Content-Length
标头的重要性。如果您使用
pv
监控下载进度,它将正确估计剩余时间,这是一个不错的功能:
public class ThrottlingInputStream extends InputStream {
private final InputStream target;
private final RateLimiter maxBytesPerSecond;
public ThrottlingInputStream(InputStream target, RateLimiter maxBytesPerSecond) {
this.target = target;
this.maxBytesPerSecond = maxBytesPerSecond;
}
@Override
public int read() throws IOException {
maxBytesPerSecond.acquire(1);
return target.read();
}
@Override
public int read(byte[] b) throws IOException {
maxBytesPerSecond.acquire(b.length);
return target.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
maxBytesPerSecond.acquire(len);
return target.read(b, off, len);
}
//less important below...
@Override
public long skip(long n) throws IOException {
return target.skip(n);
}
@Override
public int available() throws IOException {
return target.available();
}
@Override
public synchronized void mark(int readlimit) {
target.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
target.reset();
}
@Override
public boolean markSupported() {
return target.markSupported();
}
@Override
public void close() throws IOException {
target.close();
}
}
作为额外的奖励,
pv
证明了我们的节流效果(最后一栏)。现在没有
Content-Length
pv
对实际进度一无所知:
public class ThrottlingInputStream extends InputStream {
private final InputStream target;
private final RateLimiter maxBytesPerSecond;
public ThrottlingInputStream(InputStream target, RateLimiter maxBytesPerSecond) {
this.target = target;
this.maxBytesPerSecond = maxBytesPerSecond;
}
@Override
public int read() throws IOException {
maxBytesPerSecond.acquire(1);
return target.read();
}
@Override
public int read(byte[] b) throws IOException {
maxBytesPerSecond.acquire(b.length);
return target.read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
maxBytesPerSecond.acquire(len);
return target.read(b, off, len);
}
//less important below...
@Override
public long skip(long n) throws IOException {
return target.skip(n);
}
@Override
public int available() throws IOException {
return target.available();
}
@Override
public synchronized void mark(int readlimit) {
target.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
target.reset();
}
@Override
public boolean markSupported() {
return target.markSupported();
}
@Override
public void close() throws IOException {
target.close();
}
}
我们看到数据在流动,但不知道还剩下多少。因此 Content-Length 是一个非常重要的标头。
编写下载服务器
- 第一部分:始终流式传输,永远不会完全保留在内存中
- 第二部分:标头:Last-Modified、ETag 和 If-None-Match
- 第三部分:标题:内容长度和范围
-
第四部分:实现
HEAD
操作(高效) - 第五部分:节流下载速度
- 第六部分:描述您发送的内容(内容类型等)
GitHub 上提供了贯穿这些文章开发的 示例应用程序 。