1#[macro_use]
2extern crate bitflags;
3extern crate libc;
4extern crate time;
5
6#[macro_use]
7mod macros;
8
9pub mod cell;
10pub mod error;
11mod redis;
12
13use cell::store;
14use error::CellError;
15use libc::c_int;
16use redis::Command;
17use redis::raw;
18
19const MODULE_NAME: &str = "redis-cell";
20const MODULE_VERSION: c_int = 1;
21
22struct ThrottleCommand {}
24
25impl Command for ThrottleCommand {
26 fn name(&self) -> &'static str {
28 "cl.throttle"
29 }
30
31 fn run(&self, r: redis::Redis, args: &[&str]) -> Result<(), CellError> {
33 if args.len() != 5 && args.len() != 6 {
34 return Err(error!(
35 "Usage: {} <key> <max_burst> <count per period> \
36 <period> [<quantity>]",
37 self.name()
38 ));
39 }
40
41 let key = args[1];
43 let max_burst = parse_i64(args[2])?;
44 let count = parse_i64(args[3])?;
45 let period = parse_i64(args[4])?;
46 let quantity = match args.get(5) {
47 Some(n) => parse_i64(n)?,
48 None => 1,
49 };
50
51 let mut store = store::InternalRedisStore::new(&r);
55 let rate = cell::Rate::per_period(count, time::Duration::seconds(period));
56 let mut limiter = cell::RateLimiter::new(
57 &mut store,
58 &cell::RateQuota {
59 max_burst,
60 max_rate: rate,
61 },
62 );
63
64 let (throttled, rate_limit_result) = limiter.rate_limit(key, quantity)?;
65
66 r.reply_array(5)?;
71 r.reply_integer(if throttled { 1 } else { 0 })?;
72 r.reply_integer(rate_limit_result.limit)?;
73 r.reply_integer(rate_limit_result.remaining)?;
74 r.reply_integer(rate_limit_result.retry_after.num_seconds())?;
75 r.reply_integer(rate_limit_result.reset_after.num_seconds())?;
76
77 Ok(())
78 }
79
80 fn str_flags(&self) -> &'static str {
84 "write"
85 }
86}
87
88#[allow(non_snake_case)]
89#[allow(unused_variables)]
90#[no_mangle]
91pub extern "C" fn Throttle_RedisCommand(
92 ctx: *mut raw::RedisModuleCtx,
93 argv: *mut *mut raw::RedisModuleString,
94 argc: c_int,
95) -> raw::Status {
96 Command::harness(&ThrottleCommand {}, ctx, argv, argc)
97}
98
99#[allow(non_snake_case)]
100#[allow(unused_variables)]
101#[no_mangle]
102pub extern "C" fn RedisModule_OnLoad(
103 ctx: *mut raw::RedisModuleCtx,
104 argv: *mut *mut raw::RedisModuleString,
105 argc: c_int,
106) -> raw::Status {
107 if raw::init(
108 ctx,
109 format!("{}\0", MODULE_NAME).as_ptr(),
110 MODULE_VERSION,
111 raw::REDISMODULE_APIVER_1,
112 ) == raw::Status::Err
113 {
114 return raw::Status::Err;
115 }
116
117 let command = ThrottleCommand {};
118 if raw::create_command(
119 ctx,
120 format!("{}\0", command.name()).as_ptr(),
121 Some(Throttle_RedisCommand),
122 format!("{}\0", command.str_flags()).as_ptr(),
123 0,
124 0,
125 0,
126 ) == raw::Status::Err
127 {
128 return raw::Status::Err;
129 }
130
131 raw::Status::Ok
132}
133
134fn parse_i64(arg: &str) -> Result<i64, CellError> {
135 arg.parse::<i64>()
136 .map_err(|_| error!("Couldn't parse as integer: {}", arg))
137}