Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | 28x 28x 28x 28x 28x 28x | import { Binding } from '@/core/bindings/Binding'
import { lazySettings } from '@/utils/lazySettings'
import { RateLimitSettings } from '@/types/RateLimitSettings'
import type { CloudflareEnv } from '@/types/CloudflareEnv'
/**
* Accessor for a rate limiting binding (`ratelimits` in `wrangler.json`).
*
* @remarks
* Unlike every other binding here, the interesting configuration is not in
* `src/config/` and cannot be: the rate and the window are fixed in
* `wrangler.json` at deploy time, and the runtime API takes only the key to
* count against. A Worker that needs two different rates declares two bindings
* and constructs one of these per binding.
*
* The limiter is eventually consistent and scoped to a Cloudflare location, so
* the effective global rate is higher than the configured one — by roughly the
* number of locations seeing traffic. It is an abuse control, not an accounting
* record: never use it for anything that must balance, like a quota a customer
* is billed against.
*
* @example
* ```ts
* const limiter = new RateLimitBinding() // binding name from the configuration layer
* const { success } = await limiter.limit(ctx.env, ip)
* ```
*
* @class
*
* @author Bayu Dwiyan Satria
* @version 1.0.0
* @since 1.0.0
*/
export class RateLimitBinding<E extends CloudflareEnv = CloudflareEnv> extends Binding<RateLimit, E> {
/**
* This accessor’s settings, resolved on first read.
*/
private readonly settings: () => RateLimitSettings
/**
* Whether an unbound limiter admits the request.
*/
private readonly failOpen: boolean
/**
* Constructs a RateLimitBinding instance.
*
* @param settings Resolved settings. Defaults to `resolve('rateLimit')`, so
* callers normally construct this with no arguments at all.
*/
constructor(settings?: RateLimitSettings) {
const settle = lazySettings<RateLimitSettings>('rateLimit', settings)
super(() => settle().binding)
this.settings = settle
}
/**
* Counts one request against a key and reports whether it is within the rate.
*
* @remarks
* The key decides what is being limited, and choosing it is the whole design:
* a client IP limits one caller, a conversation id limits one conversation, a
* route name limits everyone at once. Cloudflare hashes it, so it may carry
* an identifier — but it is still a key in a shared namespace, so prefix it
* when one binding limits more than one thing.
*
* Calling this *is* the increment. There is no way to ask without counting,
* which means a request must be checked exactly once.
*
* @param env The Worker environment.
* @param key What to count against — an IP, a user id, a route name.
* @returns `true` when the request is within the rate, `false` when it is
* over. Returns {@link RateLimitBinding.failOpen} when the binding is
* absent.
*/
public async limit(env: E, key: string): Promise<boolean> {
const limiter = this.tryResolve(env)
if (limiter === null) {
return this.settings().failOpen
}
const outcome = await limiter.limit({ key })
return outcome.success
}
}
|