gisila_redis
gisila_redis is a small companion package for the gisila web framework.
It gives a gisila app a rate-limit store and a cache that are shared by every isolate and
server on the same Redis. It is separate from gisila_queue: the queue’s Redis components
use package:redis directly and don’t depend on it.
dependencies: gisila_redis: ^0.1.0It works with gisila >=0.3.0 <0.5.0.
import 'dart:io';
import 'package:gisila/gisila.dart';import 'package:gisila_redis/gisila_redis.dart';
final redis = GisilaRedis.fromUrl(Platform.environment['REDIS_URL']!);
final app = GisilaApp( config: AppConfig( rateLimit: const RateLimitConfig(requestsPerMinute: 300), rateLimitStore: RedisRateLimitStore(redis), cache: RedisCacheProvider(redis), // connects at start(), closes at stop() ),);GisilaRedis
Section titled “GisilaRedis”A small Redis client: one pipelined connection, opened lazily, replaced after a timeout or socket error, with a timeout on every command.
class GisilaRedis { GisilaRedis({ String host = 'localhost', int port = 6379, String? username, String? password, int database = 0, bool tls = false, SecurityContext? securityContext, Duration connectTimeout = const Duration(seconds: 5), Duration commandTimeout = const Duration(seconds: 2), });
factory GisilaRedis.fromUrl(String url, {SecurityContext? securityContext, Duration connectTimeout = const Duration(seconds: 5), Duration commandTimeout = const Duration(seconds: 2)});
Future<Object?> send(List<Object> command); Future<void> connect(); Future<void> close();}When to use: to build the two stores below, and for any other Redis command your web app needs (share one client). It is not used by gisila_queue.
| Parameter / member | Type | Default | Description |
|---|---|---|---|
host, port |
'localhost', 6379 |
Address. | |
username, password |
String? |
null |
Sent with AUTH on connect. |
database |
int |
0 |
SELECTed when not 0. |
tls, securityContext |
false, null |
TLS and a private CA or client cert. | |
connectTimeout |
Duration |
5 s | Bounds connecting. |
commandTimeout |
Duration |
2 s | Bounds each command; on timeout the connection is dropped and the next command reconnects. Keep it short, rate limiting runs on every request. |
fromUrl(url) |
redis://[user[:password]@]host[:port][/db] or rediss://. A lone credential is the password. Other schemes throw FormatException. |
||
send(command) |
Future<Object?> |
Sends one command such as ['GET', 'key'] and returns the reply. Throws RedisException. |
|
connect() |
Future<void> |
Sends PING so a wrong address or password fails at startup. |
|
close() |
Future<void> |
Closes the connection; the client can’t be used afterwards. |
final redis = GisilaRedis(host: 'localhost', password: 'secret');await redis.send(['SET', 'greeting', 'hello', 'EX', '60']);final value = await redis.send(['GET', 'greeting']) as String?;RedisRateLimitStore
Section titled “RedisRateLimitStore”A gisila RateLimitStore with token buckets in Redis.
class RedisRateLimitStore implements RateLimitStore { RedisRateLimitStore( GisilaRedis redis, { String prefix = 'gisila:rl:', bool failOpen = true, void Function(Object error)? onError, Duration failClosedRetry = const Duration(seconds: 1), });
Future<Duration?> take(String key, {required double capacity, required double refillPerSecond});}When to use: whenever a gisila app runs more than one isolate or server. Without it,
each isolate has its own buckets, so N isolates allow N times the configured rate. With
it, AppConfig.rateLimit and per-route RateLimitConfig share one bucket per client.
| Parameter | Type | Default | Description |
|---|---|---|---|
redis |
GisilaRedis |
required | Client. |
prefix |
String |
'gisila:rl:' |
Prepended to every bucket key. |
failOpen |
bool |
true |
When Redis fails, let the request through. Set false for abuse limits (login attempts); those requests then get 429. |
onError |
void Function(Object)? |
null |
Called on every Redis failure. |
failClosedRetry |
Duration |
1 s | Wait reported when failing closed. |
Each take is one atomic Lua script (EVALSHA, reloaded after NOSCRIPT) that uses
Redis’s clock, so servers with drifting clocks share one budget. Buckets expire once they
would be full again. take returns null when a token was taken, or how long to wait.
RedisCacheProvider
Section titled “RedisCacheProvider”A gisila CacheProvider (AppConfig.cache, ctx.cache in handlers) in Redis.
class RedisCacheProvider extends CacheProvider { RedisCacheProvider(GisilaRedis redis, {String prefix = 'gisila:cache:'});}When to use: a cache shared by all isolates and servers. Use gisila’s in-memory cache provider for a single isolate or tests.
| Member | Behaviour |
|---|---|
prefix |
Every key is stored under it, so apps can share a Redis. |
connect() / disconnect() |
Called by gisila at start() / stop(). disconnect closes the GisilaRedis client. |
get, set(key, value, ttl:), delete, exists |
GET, SET … PX, DEL, EXISTS. |
keys(pattern) |
SCAN inside the prefix (never KEYS); returns keys without the prefix. |
flush() |
Deletes only this prefix’s keys, found with SCAN, in batches of 500. Never FLUSHDB. |
increment(key, by:) / decrement |
Atomic INCRBY. Throws FormatException if the value isn’t an integer. |
mget(keys) / mdelete(keys) |
One round trip each. Missing keys are left out of mget’s map. |
RedisException
Section titled “RedisException”class RedisException implements Exception { RedisException(String message); final String message;}Every failed send: an error reply (connection kept), a timeout, or a lost connection
(connection dropped and reopened on the next command).
See also: gisila framework, Use the queue in a gisila app.