58 lines
2.2 KiB
C#
58 lines
2.2 KiB
C#
namespace w4c_workflows.Services.Security;
|
|
|
|
/// <summary>
|
|
/// Enforces <see cref="NodeQuotaOptions"/> during a run. The request budget is
|
|
/// kept in the run-scoped <c>NodeExecutionContext.State</c> under
|
|
/// <see cref="RequestCounterKey"/>, so the count is shared by every HTTP node in
|
|
/// the same run and is created fresh per run (and per sub-workflow run).
|
|
/// </summary>
|
|
public sealed class NodeQuotaPolicy
|
|
{
|
|
/// <summary>Run-state key holding the outbound request counter.</summary>
|
|
public const string RequestCounterKey = "node.quota.outboundRequests";
|
|
|
|
private readonly NodeQuotaOptions _options;
|
|
|
|
public NodeQuotaPolicy(NodeQuotaOptions options) => _options = options;
|
|
|
|
/// <summary>Response-body cap in bytes; <c>0</c> means unlimited.</summary>
|
|
public long MaxResponseBytes => _options.MaxResponseBytes;
|
|
|
|
/// <summary>Outbound-request cap per run; <c>0</c> means unlimited.</summary>
|
|
public int MaxRequestsPerRun => _options.MaxRequestsPerRun;
|
|
|
|
/// <summary>
|
|
/// Reserves one outbound request from the run budget. Returns <c>false</c>
|
|
/// once the run has consumed its allowance; <paramref name="used"/> is the
|
|
/// post-increment count so the caller can report it. The counter is a single
|
|
/// mutable object in <paramref name="state"/>, so concurrent item execution
|
|
/// still reserves exactly once per request.
|
|
/// </summary>
|
|
public bool TryReserveRequest(IDictionary<string, object?> state, out int used)
|
|
{
|
|
used = 0;
|
|
if (_options.MaxRequestsPerRun <= 0)
|
|
return true;
|
|
|
|
if (!state.TryGetValue(RequestCounterKey, out var existing) || existing is not RequestCounter counter)
|
|
{
|
|
counter = new RequestCounter();
|
|
state[RequestCounterKey] = counter;
|
|
}
|
|
|
|
used = counter.Increment();
|
|
return used <= _options.MaxRequestsPerRun;
|
|
}
|
|
|
|
/// <summary>True when a response of <paramref name="length"/> bytes fits the cap.</summary>
|
|
public bool IsResponseWithinLimit(long length)
|
|
=> _options.MaxResponseBytes <= 0 || length <= _options.MaxResponseBytes;
|
|
|
|
private sealed class RequestCounter
|
|
{
|
|
private int _value;
|
|
|
|
public int Increment() => Interlocked.Increment(ref _value);
|
|
}
|
|
}
|