feat(api): count upload frames separately and send Retry-After when rate limited
This commit is contained in:
1 parent
82d55a97f9
commit
96c4335cb5
2 files changed
+15
-3
No files matched your search
@@ -68,7 +68,7 @@ public static class WebDropApp
|
||||
"X-WD-Role", "X-WD-Endpoint", "X-WD-Cap", "X-WD-Max-Downloads", "X-WD-Account",
|
||||
"X-WD-Event-Id", "X-WD-Event-Kind", "X-WD-Event-Channel", "X-WD-Event-Domain",
|
||||
"X-WD-Event-Epoch", "X-WD-Event-Frames", "X-WD-Event-Manifest", "X-WD-Event-Signature", "X-WD-Event-Target")
|
||||
.WithExposedHeaders("X-WD-Last-Seq")
|
||||
.WithExposedHeaders("X-WD-Last-Seq", "Retry-After")
|
||||
.SetPreflightMaxAge(TimeSpan.FromHours(1))));
|
||||
}
|
||||
|
||||
@@ -76,13 +76,17 @@ public static class WebDropApp
|
||||
{
|
||||
r.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
// 限流状态只在内存里,按 IP 分区,不落盘
|
||||
r.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(ctx =>
|
||||
RateLimitPartition.GetFixedWindowLimiter(ClientKey(ctx), _ => PerMinute(options.RequestsPerMinute)));
|
||||
r.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(ctx => IsFrameUpload(ctx.Request)
|
||||
? RateLimitPartition.GetFixedWindowLimiter("frames:" + ClientKey(ctx), _ => PerMinute(options.FrameRequestsPerMinute))
|
||||
: RateLimitPartition.GetFixedWindowLimiter(ClientKey(ctx), _ => PerMinute(options.RequestsPerMinute)));
|
||||
r.AddPolicy(Endpoints.CreatePolicy, ctx =>
|
||||
RateLimitPartition.GetFixedWindowLimiter(ClientKey(ctx), _ => PerMinute(options.ChannelCreatesPerMinute)));
|
||||
r.OnRejected = async (context, ct) =>
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
||||
// 告诉客户端多久后窗口重置,弱网和上传重试据此等待,不再盲目重试
|
||||
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
|
||||
context.HttpContext.Response.Headers.RetryAfter = Math.Max(1, (int)Math.Ceiling(retryAfter.TotalSeconds)).ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
await context.HttpContext.Response.WriteAsJsonAsync(
|
||||
new ErrorBody { Error = ErrorCode.RateLimited.ToWire() }, ApiJson.Default.ErrorBody, cancellationToken: ct);
|
||||
};
|
||||
@@ -134,6 +138,9 @@ public static class WebDropApp
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFrameUpload(HttpRequest request) =>
|
||||
HttpMethods.IsPost(request.Method) && request.Path.Value is { } path && path.EndsWith("/frames", StringComparison.Ordinal);
|
||||
|
||||
private static string ClientKey(HttpContext ctx) => ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
|
||||
private static FixedWindowRateLimiterOptions PerMinute(int permits) => new()
|
||||
|
||||
@@ -21,6 +21,11 @@ public sealed class WebDropOptions
|
||||
public string? PasskeyRpId { get; set; }
|
||||
|
||||
public int RequestsPerMinute { get; set; } = 600;
|
||||
/// <summary>
|
||||
/// 上传帧(POST .../frames)单独计数。每帧最多约 1 MiB,和普通 API 请求放在一个 600/分钟的桶里,
|
||||
/// 持续上传速度就被锁在约 10 MiB/s;帧的总量另有空间配额和单条上限保护。
|
||||
/// </summary>
|
||||
public int FrameRequestsPerMinute { get; set; } = 6000;
|
||||
public int ChannelCreatesPerMinute { get; set; } = 30;
|
||||
public int ShareDraftTtlSeconds { get; set; } = 86_400;
|
||||
public int SweepIntervalSeconds { get; set; } = 30;
|
||||
|
||||
Reference in new issue
Block a user