[feat] Support redis call with wasm-rust (#1417)

This commit is contained in:
007gzs
2024-10-29 19:35:02 +08:00
committed by GitHub
parent 93c1e5c2bb
commit 2219a17898
18 changed files with 1698 additions and 154 deletions

View File

@@ -4,10 +4,12 @@ pub trait Cluster {
fn cluster_name(&self) -> String;
fn host_name(&self) -> String;
}
#[derive(Debug, Clone)]
pub struct RouteCluster {
host: String,
}
impl RouteCluster {
pub fn new(host: &str) -> Self {
RouteCluster {
@@ -15,6 +17,7 @@ impl RouteCluster {
}
}
}
impl Cluster for RouteCluster {
fn cluster_name(&self) -> String {
if let Some(res) = get_property(vec!["cluster_name"]) {
@@ -111,6 +114,7 @@ impl NacosCluster {
}
}
}
impl Cluster for NacosCluster {
fn cluster_name(&self) -> String {
let group = if self.group.is_empty() {
@@ -154,6 +158,7 @@ impl StaticIpCluster {
}
}
}
impl Cluster for StaticIpCluster {
fn cluster_name(&self) -> String {
format!("outbound|{}||{}.static", self.port, self.service_name)
@@ -184,6 +189,7 @@ impl DnsCluster {
}
}
}
impl Cluster for DnsCluster {
fn cluster_name(&self) -> String {
format!("outbound|{}||{}.dns", self.port, self.service_name)
@@ -212,6 +218,7 @@ impl ConsulCluster {
}
}
}
impl Cluster for ConsulCluster {
fn cluster_name(&self) -> String {
format!(
@@ -245,10 +252,12 @@ impl FQDNCluster {
}
}
}
impl Cluster for FQDNCluster {
fn cluster_name(&self) -> String {
format!("outbound|{}||{}", self.port, self.fqdn)
}
fn host_name(&self) -> String {
if self.host.is_empty() {
self.fqdn.clone()

View File

@@ -28,29 +28,15 @@
/// However, in the rules of event and field, there is an ambiguous grammar in the judgment of eol,
/// and it will bring ambiguity (whether the field ends). In order to eliminate this ambiguity,
/// we believe that CRLF as CR+LF belongs to event and field respectively.
#[derive(Default)]
pub struct EventStream {
buffer: Vec<u8>,
processed_offset: usize,
}
impl EventStream {
pub fn new() -> Self {
EventStream {
buffer: Vec::new(),
processed_offset: 0,
}
}
/// Update the event stream by adding new data to the buffer and resetting processed offset if needed.
pub fn update(&mut self, data: Vec<u8>) {
if self.processed_offset > 0 {
self.buffer.drain(0..self.processed_offset);
self.processed_offset = 0;
}
self.buffer.extend(data);
}
impl Iterator for EventStream {
type Item = Vec<u8>;
/// Get the next event from the event stream. Return the event data if available, otherwise return None.
/// Next will consume all the data in the current buffer. However, if there is a valid event at the end of the buffer,
/// it will return the event directly even if the data after the next `update` could be considered part of the same event
@@ -71,7 +57,8 @@ impl EventStream {
/// }
/// }
/// ```
pub fn next(&mut self) -> Option<Vec<u8>> {
///
fn next(&mut self) -> Option<Self::Item> {
let mut i = self.processed_offset;
while i < self.buffer.len() {
@@ -86,6 +73,18 @@ impl EventStream {
None
}
}
impl EventStream {
/// Update the event stream by adding new data to the buffer and resetting processed offset if needed.
pub fn update(&mut self, data: Vec<u8>) {
if self.processed_offset > 0 {
self.buffer.drain(0..self.processed_offset);
self.processed_offset = 0;
}
self.buffer.extend(data);
}
/// Flush the event stream and return any remaining unprocessed event data. Return None if there is none.
pub fn flush(&mut self) -> Option<Vec<u8>> {
@@ -138,7 +137,7 @@ mod tests {
#[test]
fn test_crlf_events() {
let mut parser = EventStream::new();
let mut parser = EventStream::default();
parser.update(b"event1\n\nevent2\n\n".to_vec());
assert_eq!(parser.next(), Some(b"event1".to_vec()));
@@ -147,7 +146,7 @@ mod tests {
#[test]
fn test_lf_events() {
let mut parser = EventStream::new();
let mut parser = EventStream::default();
parser.update(b"event3\n\r\nevent4\r\n".to_vec());
assert_eq!(parser.next(), Some(b"event3".to_vec()));
@@ -156,7 +155,7 @@ mod tests {
#[test]
fn test_partial_event() {
let mut parser = EventStream::new();
let mut parser = EventStream::default();
parser.update(b"partial_event1".to_vec());
assert_eq!(parser.next(), None);
@@ -167,7 +166,7 @@ mod tests {
#[test]
fn test_mixed_eol_events() {
let mut parser = EventStream::new();
let mut parser = EventStream::default();
parser.update(b"event5\r\nevent6\r\n\r\nevent7\r\n".to_vec());
assert_eq!(parser.next(), Some(b"event5".to_vec()));
@@ -177,7 +176,7 @@ mod tests {
#[test]
fn test_mixed2_eol_events() {
let mut parser = EventStream::new();
let mut parser = EventStream::default();
parser.update(b"event5\r\nevent6\r\n".to_vec());
assert_eq!(parser.next(), Some(b"event5".to_vec()));
assert_eq!(parser.next(), Some(b"event6".to_vec()));
@@ -188,7 +187,7 @@ mod tests {
#[test]
fn test_no_event() {
let mut parser = EventStream::new();
let mut parser = EventStream::default();
parser.update(b"no_eol_in_this_string".to_vec());
assert_eq!(parser.next(), None);

View File

@@ -14,7 +14,7 @@
#![allow(dead_code)]
use proxy_wasm::hostcalls;
use proxy_wasm::hostcalls::{self, RedisCallbackFn};
use proxy_wasm::types::{BufferType, Bytes, MapType, Status};
use std::time::{Duration, SystemTime};
@@ -381,3 +381,24 @@ pub(crate) fn send_http_response(
) {
hostcalls::send_http_response(status_code, headers, body).unwrap()
}
pub(crate) fn redis_init(
upstream: &str,
username: Option<&[u8]>,
password: Option<&[u8]>,
timeout: Duration,
) -> Result<(), Status> {
hostcalls::redis_init(upstream, username, password, timeout)
}
pub(crate) fn dispatch_redis_call(
upstream: &str,
query: &[u8],
call_fn: Box<RedisCallbackFn>,
) -> Result<u32, Status> {
hostcalls::dispatch_redis_call(upstream, query, call_fn)
}
pub(crate) fn get_redis_call_response(start: usize, max_size: usize) -> Option<Bytes> {
hostcalls::get_buffer(BufferType::RedisCallResponse, start, max_size).unwrap()
}

View File

@@ -18,5 +18,6 @@ pub mod event_stream;
mod internal;
pub mod log;
pub mod plugin_wrapper;
pub mod redis_wrapper;
pub mod request_wrapper;
pub mod rule_matcher;

View File

@@ -30,6 +30,7 @@ use serde::de::DeserializeOwned;
lazy_static! {
static ref LOG: Log = Log::new("plugin_wrapper".to_string());
}
thread_local! {
static HTTP_CALLBACK_DISPATCHER: HttpCallbackDispatcher = HttpCallbackDispatcher::new();
}
@@ -49,7 +50,9 @@ where
None => None,
}
}
fn rule_matcher(&self) -> &SharedRuleMatcher<PluginConfig>;
fn create_http_context_wrapper(
&self,
_context_id: u32,
@@ -63,20 +66,24 @@ pub type HttpCallbackFn = dyn FnOnce(u16, &MultiMap<String, String>, Option<Vec<
pub struct HttpCallbackDispatcher {
call_fns: RefCell<HashMap<u32, Box<HttpCallbackFn>>>,
}
impl Default for HttpCallbackDispatcher {
fn default() -> Self {
Self::new()
}
}
impl HttpCallbackDispatcher {
pub fn new() -> Self {
HttpCallbackDispatcher {
call_fns: RefCell::new(HashMap::new()),
}
}
pub fn set(&self, token_id: u32, arg: Box<HttpCallbackFn>) {
self.call_fns.borrow_mut().insert(token_id, arg);
}
pub fn pop(&self, token_id: u32) -> Option<Box<HttpCallbackFn>> {
self.call_fns.borrow_mut().remove(&token_id)
}
@@ -91,31 +98,39 @@ where
_self_weak: Weak<RefCell<Box<dyn HttpContextWrapper<PluginConfig>>>>,
) {
}
fn log(&self) -> &Log {
&LOG
}
fn on_config(&mut self, _config: Rc<PluginConfig>) {}
fn on_http_request_complete_headers(
&mut self,
_headers: &MultiMap<String, String>,
) -> HeaderAction {
HeaderAction::Continue
}
fn on_http_response_complete_headers(
&mut self,
_headers: &MultiMap<String, String>,
) -> HeaderAction {
HeaderAction::Continue
}
fn cache_request_body(&self) -> bool {
false
}
fn cache_response_body(&self) -> bool {
false
}
fn on_http_request_complete_body(&mut self, _req_body: &Bytes) -> DataAction {
DataAction::Continue
}
fn on_http_response_complete_body(&mut self, _res_body: &Bytes) -> DataAction {
DataAction::Continue
}
@@ -123,6 +138,7 @@ where
fn replace_http_request_body(&mut self, body: &[u8]) {
self.set_http_request_body(0, i32::MAX as usize, body)
}
fn replace_http_response_body(&mut self, body: &[u8]) {
self.set_http_response_body(0, i32::MAX as usize, body)
}
@@ -164,8 +180,8 @@ where
if let Ok(token_id) = ret {
HTTP_CALLBACK_DISPATCHER.with(|dispatcher| dispatcher.set(token_id, call_fn));
self.log().debug(
&format!(
self.log().debugf(
format_args!(
"http call start, id: {}, cluster: {}, method: {}, url: {}, body: {:?}, timeout: {:?}",
token_id, cluster.cluster_name(), method.as_str(), raw_url, body, timeout
)
@@ -173,7 +189,8 @@ where
}
ret
} else {
self.log().critical(&format!("invalid raw_url:{}", raw_url));
self.log()
.criticalf(format_args!("invalid raw_url:{}", raw_url));
Err(Status::ParseFailure)
}
}
@@ -182,14 +199,13 @@ where
downcast_rs::impl_downcast!(HttpContextWrapper<PluginConfig> where PluginConfig: Default + DeserializeOwned + Clone);
pub struct PluginHttpWrapper<PluginConfig> {
req_headers: MultiMap<String, String>,
res_headers: MultiMap<String, String>,
req_body_len: usize,
res_body_len: usize,
config: Option<Rc<PluginConfig>>,
rule_matcher: SharedRuleMatcher<PluginConfig>,
http_content: Rc<RefCell<Box<dyn HttpContextWrapper<PluginConfig>>>>,
}
impl<PluginConfig> PluginHttpWrapper<PluginConfig>
where
PluginConfig: Default + DeserializeOwned + Clone + 'static,
@@ -203,8 +219,6 @@ where
.borrow_mut()
.init_self_weak(Rc::downgrade(&rc_content));
PluginHttpWrapper {
req_headers: MultiMap::new(),
res_headers: MultiMap::new(),
req_body_len: 0,
res_body_len: 0,
config: None,
@@ -212,10 +226,12 @@ where
http_content: rc_content,
}
}
fn get_http_call_fn(&mut self, token_id: u32) -> Option<Box<HttpCallbackFn>> {
HTTP_CALLBACK_DISPATCHER.with(|dispatcher| dispatcher.pop(token_id))
}
}
impl<PluginConfig> Context for PluginHttpWrapper<PluginConfig>
where
PluginConfig: Default + DeserializeOwned + Clone + 'static,
@@ -240,24 +256,24 @@ where
status_code = code;
normal_response = true;
} else {
self.http_content
.borrow()
.log()
.error(&format!("failed to parse status: {}", header_value));
self.http_content.borrow().log().errorf(format_args!(
"failed to parse status: {}",
header_value
));
status_code = 500;
}
}
headers.insert(k, header_value);
}
Err(_) => {
self.http_content.borrow().log().warn(&format!(
self.http_content.borrow().log().warnf(format_args!(
"http call response header contains non-ASCII characters header: {}",
k
));
}
}
}
self.http_content.borrow().log().warn(&format!(
self.http_content.borrow().log().debugf(format_args!(
"http call end, id: {}, code: {}, normal: {}, body: {:?}", /* */
token_id, status_code, normal_response, body
));
@@ -277,21 +293,25 @@ where
.borrow_mut()
.on_grpc_call_response(token_id, status_code, response_size)
}
fn on_grpc_stream_initial_metadata(&mut self, token_id: u32, num_elements: u32) {
self.http_content
.borrow_mut()
.on_grpc_stream_initial_metadata(token_id, num_elements)
}
fn on_grpc_stream_message(&mut self, token_id: u32, message_size: usize) {
self.http_content
.borrow_mut()
.on_grpc_stream_message(token_id, message_size)
}
fn on_grpc_stream_trailing_metadata(&mut self, token_id: u32, num_elements: u32) {
self.http_content
.borrow_mut()
.on_grpc_stream_trailing_metadata(token_id, num_elements)
}
fn on_grpc_stream_close(&mut self, token_id: u32, status_code: u32) {
self.http_content
.borrow_mut()
@@ -302,6 +322,7 @@ where
self.http_content.borrow_mut().on_done()
}
}
impl<PluginConfig> HttpContext for PluginHttpWrapper<PluginConfig>
where
PluginConfig: Default + DeserializeOwned + Clone + 'static,
@@ -312,13 +333,15 @@ where
if self.config.is_none() {
return HeaderAction::Continue;
}
let mut req_headers = MultiMap::new();
for (k, v) in self.get_http_request_headers_bytes() {
match String::from_utf8(v) {
Ok(header_value) => {
self.req_headers.insert(k, header_value);
req_headers.insert(k, header_value);
}
Err(_) => {
self.http_content.borrow().log().warn(&format!(
self.http_content.borrow().log().warnf(format_args!(
"request http header contains non-ASCII characters header: {}",
k
));
@@ -338,7 +361,7 @@ where
}
self.http_content
.borrow_mut()
.on_http_request_complete_headers(&self.req_headers)
.on_http_request_complete_headers(&req_headers)
}
fn on_http_request_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction {
@@ -383,13 +406,15 @@ where
if self.config.is_none() {
return HeaderAction::Continue;
}
let mut res_headers = MultiMap::new();
for (k, v) in self.get_http_response_headers_bytes() {
match String::from_utf8(v) {
Ok(header_value) => {
self.res_headers.insert(k, header_value);
res_headers.insert(k, header_value);
}
Err(_) => {
self.http_content.borrow().log().warn(&format!(
self.http_content.borrow().log().warnf(format_args!(
"response http header contains non-ASCII characters header: {}",
k
));
@@ -406,7 +431,7 @@ where
}
self.http_content
.borrow_mut()
.on_http_response_complete_headers(&self.res_headers)
.on_http_response_complete_headers(&res_headers)
}
fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction {

View File

@@ -0,0 +1,724 @@
use std::{collections::HashMap, time::Duration};
use proxy_wasm::{hostcalls::RedisCallbackFn, types::Status};
use redis::{Cmd, ToRedisArgs, Value};
use crate::{cluster_wrapper::Cluster, internal};
pub type RedisValueCallbackFn = dyn FnOnce(&Result<Value, String>, usize, u32);
fn gen_callback(call_fn: Box<RedisValueCallbackFn>) -> Box<RedisCallbackFn> {
Box::new(move |token_id, status, response_size| {
let res = match internal::get_redis_call_response(0, response_size) {
Some(data) => match redis::parse_redis_value(&data) {
Ok(v) => Ok(v),
Err(e) => Err(e.to_string()),
},
None => Err("response data not found".to_string()),
};
call_fn(&res, status, token_id);
})
}
pub struct RedisClientBuilder {
upstream: String,
username: Option<String>,
password: Option<String>,
timeout: Duration,
}
impl RedisClientBuilder {
pub fn new(cluster: &dyn Cluster, timeout: Duration) -> Self {
RedisClientBuilder {
upstream: cluster.cluster_name(),
username: None,
password: None,
timeout,
}
}
pub fn username<T: AsRef<str>>(mut self, username: Option<T>) -> Self {
self.username = username.map(|u| u.as_ref().to_string());
self
}
pub fn password<T: AsRef<str>>(mut self, password: Option<T>) -> Self {
self.password = password.map(|p| p.as_ref().to_string());
self
}
pub fn build(self) -> RedisClient {
RedisClient {
upstream: self.upstream,
username: self.username,
password: self.password,
timeout: self.timeout,
}
}
}
pub struct RedisClientConfig {
upstream: String,
username: Option<String>,
password: Option<String>,
timeout: Duration,
}
impl RedisClientConfig {
pub fn new(cluster: &dyn Cluster, timeout: Duration) -> Self {
RedisClientConfig {
upstream: cluster.cluster_name(),
username: None,
password: None,
timeout,
}
}
pub fn username<T: AsRef<str>>(&mut self, username: Option<T>) -> &Self {
self.username = username.map(|u| u.as_ref().to_string());
self
}
pub fn password<T: AsRef<str>>(&mut self, password: Option<T>) -> &Self {
self.password = password.map(|p| p.as_ref().to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct RedisClient {
upstream: String,
username: Option<String>,
password: Option<String>,
timeout: Duration,
}
impl RedisClient {
pub fn new(config: &RedisClientConfig) -> Self {
RedisClient {
upstream: config.upstream.clone(),
username: config.username.clone(),
password: config.password.clone(),
timeout: config.timeout,
}
}
pub fn init(&self) -> Result<(), Status> {
internal::redis_init(
&self.upstream,
self.username.as_ref().map(|u| u.as_bytes()),
self.password.as_ref().map(|p| p.as_bytes()),
self.timeout,
)
}
fn call(&self, query: &[u8], call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
internal::dispatch_redis_call(&self.upstream, query, gen_callback(call_fn))
}
pub fn command(&self, cmd: &Cmd, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
self.call(&cmd.get_packed_command(), call_fn)
}
pub fn eval<T: ToRedisArgs>(
&self,
script: &str,
numkeys: i32,
keys: Vec<&str>,
args: Vec<T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("eval");
cmd.arg(script).arg(numkeys);
for key in keys {
cmd.arg(key);
}
for arg in args {
cmd.arg(arg);
}
self.command(&cmd, call_fn)
}
// Key
pub fn del(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("del");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn exists(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("exists");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn expire(
&self,
key: &str,
ttl: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("expire");
cmd.arg(key).arg(ttl);
self.command(&cmd, call_fn)
}
pub fn persist(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("persist");
cmd.arg(key);
self.command(&cmd, call_fn)
}
// String
pub fn get(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("get");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn set<T: ToRedisArgs>(
&self,
key: &str,
value: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("set");
cmd.arg(key).arg(value);
self.command(&cmd, call_fn)
}
pub fn setex<T: ToRedisArgs>(
&self,
key: &str,
value: T,
ttl: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("setex");
cmd.arg(key).arg(ttl).arg(value);
self.command(&cmd, call_fn)
}
pub fn mget(&self, keys: Vec<&str>, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("mget");
for key in keys {
cmd.arg(key);
}
self.command(&cmd, call_fn)
}
pub fn mset<T: ToRedisArgs>(
&self,
kv_map: HashMap<&str, T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("mset");
for (k, v) in kv_map {
cmd.arg(k).arg(v);
}
self.command(&cmd, call_fn)
}
pub fn incr(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("incr");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn decr(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("decr");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn incrby(
&self,
key: &str,
delta: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("incrby");
cmd.arg(key).arg(delta);
self.command(&cmd, call_fn)
}
pub fn decrby(
&self,
key: &str,
delta: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("decrby");
cmd.arg(key).arg(delta);
self.command(&cmd, call_fn)
}
// List
pub fn llen(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("llen");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn rpush<T: ToRedisArgs>(
&self,
key: &str,
vals: Vec<T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("rpush");
cmd.arg(key);
for val in vals {
cmd.arg(val);
}
self.command(&cmd, call_fn)
}
pub fn rpop(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("rpop");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn lpush<T: ToRedisArgs>(
&self,
key: &str,
vals: Vec<T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("lpush");
cmd.arg(key);
for val in vals {
cmd.arg(val);
}
self.command(&cmd, call_fn)
}
pub fn lpop(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("lpop");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn lindex(
&self,
key: &str,
index: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("lindex");
cmd.arg(key).arg(index);
self.command(&cmd, call_fn)
}
pub fn lrange(
&self,
key: &str,
start: i32,
stop: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("lrange");
cmd.arg(key).arg(start).arg(stop);
self.command(&cmd, call_fn)
}
pub fn lrem<T: ToRedisArgs>(
&self,
key: &str,
count: i32,
value: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("lrem");
cmd.arg(key).arg(count).arg(value);
self.command(&cmd, call_fn)
}
pub fn linsert_before<T: ToRedisArgs>(
&self,
key: &str,
pivot: T,
value: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("linsert");
cmd.arg(key).arg("before").arg(pivot).arg(value);
self.command(&cmd, call_fn)
}
pub fn linsert_after<T: ToRedisArgs>(
&self,
key: &str,
pivot: T,
value: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("linsert");
cmd.arg(key).arg("after").arg(pivot).arg(value);
self.command(&cmd, call_fn)
}
// Hash
pub fn hexists(
&self,
key: &str,
field: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hexists");
cmd.arg(key).arg(field);
self.command(&cmd, call_fn)
}
pub fn hdel(
&self,
key: &str,
fields: Vec<&str>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hdel");
cmd.arg(key);
for field in fields {
cmd.arg(field);
}
self.command(&cmd, call_fn)
}
pub fn hlen(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("hlen");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn hget(
&self,
key: &str,
field: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hget");
cmd.arg(key).arg(field);
self.command(&cmd, call_fn)
}
pub fn hset<T: ToRedisArgs>(
&self,
key: &str,
field: &str,
value: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hset");
cmd.arg(key).arg(field).arg(value);
self.command(&cmd, call_fn)
}
pub fn hmget(
&self,
key: &str,
fields: Vec<&str>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hmget");
cmd.arg(key);
for field in fields {
cmd.arg(field);
}
self.command(&cmd, call_fn)
}
pub fn hmset<T: ToRedisArgs>(
&self,
key: &str,
kv_map: HashMap<&str, T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hmset");
cmd.arg(key);
for (k, v) in kv_map {
cmd.arg(k).arg(v);
}
self.command(&cmd, call_fn)
}
pub fn hkeys(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("hkeys");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn hvals(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("hvals");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn hgetall(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("hgetall");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn hincrby(
&self,
key: &str,
field: &str,
delta: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hincrby");
cmd.arg(key).arg(field).arg(delta);
self.command(&cmd, call_fn)
}
pub fn hincrbyfloat(
&self,
key: &str,
field: &str,
delta: f64,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("hincrbyfloat");
cmd.arg(key).arg(field).arg(delta);
self.command(&cmd, call_fn)
}
// Set
pub fn scard(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("scard");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn sadd<T: ToRedisArgs>(
&self,
key: &str,
vals: Vec<T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sadd");
cmd.arg(key);
for val in vals {
cmd.arg(val);
}
self.command(&cmd, call_fn)
}
pub fn srem<T: ToRedisArgs>(
&self,
key: &str,
vals: Vec<T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("srem");
cmd.arg(key);
for val in vals {
cmd.arg(val);
}
self.command(&cmd, call_fn)
}
pub fn sismember<T: ToRedisArgs>(
&self,
key: &str,
value: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sismember");
cmd.arg(key).arg(value);
self.command(&cmd, call_fn)
}
pub fn smembers(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("smembers");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn sdiff(
&self,
key1: &str,
key2: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sdiff");
cmd.arg(key1).arg(key2);
self.command(&cmd, call_fn)
}
pub fn sdiffstore(
&self,
destination: &str,
key1: &str,
key2: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sdiffstore");
cmd.arg(destination).arg(key1).arg(key2);
self.command(&cmd, call_fn)
}
pub fn sinter(
&self,
key1: &str,
key2: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sinter");
cmd.arg(key1).arg(key2);
self.command(&cmd, call_fn)
}
pub fn sinterstore(
&self,
destination: &str,
key1: &str,
key2: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sinterstore");
cmd.arg(destination).arg(key1).arg(key2);
self.command(&cmd, call_fn)
}
pub fn sunion(
&self,
key1: &str,
key2: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sunion");
cmd.arg(key1).arg(key2);
self.command(&cmd, call_fn)
}
pub fn sunion_store(
&self,
destination: &str,
key1: &str,
key2: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("sunionstore");
cmd.arg(destination).arg(key1).arg(key2);
self.command(&cmd, call_fn)
}
// Sorted Set
pub fn zcard(&self, key: &str, call_fn: Box<RedisValueCallbackFn>) -> Result<u32, Status> {
let mut cmd = redis::cmd("zcard");
cmd.arg(key);
self.command(&cmd, call_fn)
}
pub fn zadd<T: ToRedisArgs>(
&self,
key: &str,
ms_map: HashMap<&str, T>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zadd");
cmd.arg(key);
for (m, s) in ms_map {
cmd.arg(s).arg(m);
}
self.command(&cmd, call_fn)
}
pub fn zcount<T: ToRedisArgs>(
&self,
key: &str,
min: T,
max: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zcount");
cmd.arg(key).arg(min).arg(max);
self.command(&cmd, call_fn)
}
pub fn zincrby<T: ToRedisArgs>(
&self,
key: &str,
member: &str,
delta: T,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zincrby");
cmd.arg(key).arg(delta).arg(member);
self.command(&cmd, call_fn)
}
pub fn zscore(
&self,
key: &str,
member: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zscore");
cmd.arg(key).arg(member);
self.command(&cmd, call_fn)
}
pub fn zrank(
&self,
key: &str,
member: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zrank");
cmd.arg(key).arg(member);
self.command(&cmd, call_fn)
}
pub fn zrev_rank(
&self,
key: &str,
member: &str,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zrevrank");
cmd.arg(key).arg(member);
self.command(&cmd, call_fn)
}
pub fn zrem(
&self,
key: &str,
members: Vec<&str>,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zrem");
cmd.arg(key);
for member in members {
cmd.arg(member);
}
self.command(&cmd, call_fn)
}
pub fn zrange(
&self,
key: &str,
start: i32,
stop: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zrange");
cmd.arg(key).arg(start).arg(stop);
self.command(&cmd, call_fn)
}
pub fn zrevrange(
&self,
key: &str,
start: i32,
stop: i32,
call_fn: Box<RedisValueCallbackFn>,
) -> Result<u32, Status> {
let mut cmd = redis::cmd("zrevrange");
cmd.arg(key).arg(start).arg(stop);
self.command(&cmd, call_fn)
}
}

View File

@@ -14,6 +14,7 @@ fn get_request_head(head: &str, log_flag: &str) -> String {
String::new()
}
}
pub fn get_request_scheme() -> String {
get_request_head(":scheme", "head")
}
@@ -57,6 +58,7 @@ pub fn is_binary_response_body() -> bool {
}
false
}
pub fn has_request_body() -> bool {
let content_type = internal::get_http_request_header("content-type");
let content_length_str = internal::get_http_request_header("content-length");