├── .clog.toml ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── appveyor.yml ├── benches └── regression.rs ├── examples └── serialization.rs ├── src ├── client.rs ├── constants.rs ├── errors.rs ├── lib.rs └── protocol.rs └── tests └── client.rs /.clog.toml: -------------------------------------------------------------------------------- 1 | [clog] 2 | repository = "https://github.com/jaysonsantos/bmemcached-rs" 3 | link-style = "github" 4 | changelog = "CHANGELOG.md" 5 | output-format = "markdown" 6 | from-latest-tag = false 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | trim_trailing_whitespace = true 3 | end_of_line = lf 4 | insert_final_newline = true 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 14 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 15 | Cargo.lock 16 | 17 | # Emacs files 18 | *# 19 | .#* 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | services: 11 | - memcached 12 | env: 13 | - RUST_TEST_THREADS=1 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.5.0 (2018-07-30) 3 | 4 | 5 | #### Features 6 | 7 | * Make protocol public to allow client to choose how to use it ([eca24b94](https://github.com/jaysonsantos/bmemcached-rs/commit/eca24b9433e1a5706054109987636c0a3c9b1d08)) 8 | * Enable custom types to be used by user (#9) ([db76fd63](https://github.com/jaysonsantos/bmemcached-rs/commit/db76fd631b75e59a207e95f891f0e3150fc13c9b)) 9 | * Enable custom types to be used by user (#7) ([97d2816f](https://github.com/jaysonsantos/bmemcached-rs/commit/97d2816f044415c89e457265bbfa11a81dc55716)) 10 | * **error:** Change error system to error-chain (#11) ([ea627ee3](https://github.com/jaysonsantos/bmemcached-rs/commit/ea627ee3456f73d39c7097b1f1647039bc74f27f)) 11 | * **protocol:** 12 | * Add support to insert slices ([d9956b79](https://github.com/jaysonsantos/bmemcached-rs/commit/d9956b79ea87be8b781b1b73f08bebc03b77201c)) 13 | * Add support to InvalidArguments ([72c6f5c9](https://github.com/jaysonsantos/bmemcached-rs/commit/72c6f5c96911cba0e7feacd3b9e621253fd7f50b)) 14 | * Add error for big payloads ([3da968d7](https://github.com/jaysonsantos/bmemcached-rs/commit/3da968d73f1c42f30b116873fcec04e1f779f747)) 15 | * Make write_request write to a buffer before sending the whole payload ([0534d479](https://github.com/jaysonsantos/bmemcached-rs/commit/0534d479ebb5770c114c0e6da764860e9bb11a45)) 16 | * Make protocol's connection buffered ([3c7e9e70](https://github.com/jaysonsantos/bmemcached-rs/commit/3c7e9e70b51dbe935fff6d5594076d42230862f2)) 17 | 18 | #### Bug Fixes 19 | 20 | * **protocol:** Make sure the library wont break with big key names ([a327f208](https://github.com/jaysonsantos/bmemcached-rs/commit/a327f2081234560f32d26a95606ef300cae25cc6)) 21 | 22 | 23 | 24 | 25 | ## 0.4.0 (2017-11-01) 26 | 27 | 28 | #### Features 29 | 30 | * **error:** Change error system to error-chain (#11) ([ea627ee3](https://github.com/jaysonsantos/bmemcached-rs/commit/ea627ee3456f73d39c7097b1f1647039bc74f27f)) 31 | 32 | 33 | 34 | 35 | ### 0.3.0 (2017-09-13) 36 | 37 | 38 | #### Features 39 | 40 | * Enable custom types to be used by user (#9) ([db76fd63](https://github.com/jaysonsantos/bmemcached-rs/commit/db76fd631b75e59a207e95f891f0e3150fc13c9b)) 41 | * Enable custom types to be used by user (#7) ([97d2816f](https://github.com/jaysonsantos/bmemcached-rs/commit/97d2816f044415c89e457265bbfa11a81dc55716)) 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bmemcached" 3 | version = "0.5.0" 4 | authors = ["Jayson Reis "] 5 | description = "Memcached binary protocol in pure rust with support for 'pools' and consistent hashing. (For now minor versions will break API until v1 is released)" 6 | homepage = "https://github.com/jaysonsantos/bmemcached-rs" 7 | repository = "https://github.com/jaysonsantos/bmemcached-rs" 8 | documentation = "https://jaysonsantos.github.io/bmemcached-rs/bmemcached/index.html" 9 | license = "MIT" 10 | readme = "README.md" 11 | 12 | [dependencies] 13 | bitflags = "1.0.3" 14 | byteorder = "1.1.0" 15 | conhash = "0.4.0" 16 | error-chain = "0.12.0" 17 | enum_primitive = "0.1.0" 18 | log = "0.4.3" 19 | num = "0.2.0" 20 | 21 | [dev-dependencies] 22 | criterion = "0.2.4" 23 | env_logger = "0.5.11" 24 | serde = "1.0.70" 25 | serde_derive= "1.0.70" 26 | serde_json = "1.0.24" 27 | 28 | [[bench]] 29 | name = "regression" 30 | harness = false 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jayson Reis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bmemcached-rs 2 | 3 | [![Build Status](https://travis-ci.org/jaysonsantos/bmemcached-rs.svg?branch=master)](https://travis-ci.org/jaysonsantos/bmemcached-rs) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/ile6fh5ro8so2ctm/branch/master?svg=true)](https://ci.appveyor.com/project/jaysonsantos/bmemcached-rs/branch/master) 5 | 6 | Rust binary memcached implementation (ON GOING) 7 | 8 | # Usage 9 | ```rust 10 | extern crate bmemcached; 11 | 12 | use std::sync::Arc; 13 | use std::thread; 14 | 15 | use bmemcached::MemcachedClient; 16 | 17 | fn main() { 18 | // Use arc for threading support 19 | let client = Arc::new(MemcachedClient::new(vec!["127.0.0.1:11211"], 5).unwrap()); 20 | 21 | // Traits examples 22 | let value = "value"; 23 | client.set("string", value, 1000); 24 | let rv: String = client.get("string").unwrap(); 25 | assert_eq!(rv, "value"); 26 | 27 | client.set("integer", 10 as u8, 1000); 28 | let rv: u8 = client.get("integer").unwrap(); 29 | assert_eq!(rv, 10 as u8); 30 | 31 | // Threads example 32 | let mut threads = vec![]; 33 | for i in 0..4 { 34 | let client = client.clone(); 35 | threads.push(thread::spawn(move || { 36 | let data = format!("data_n{}", i); 37 | client.set(&data, &data, 100).unwrap(); 38 | let val: String = client.get(&data).unwrap(); 39 | client.delete(&data).unwrap(); 40 | val 41 | })); 42 | } 43 | for (i, thread) in threads.into_iter().enumerate() { 44 | let result = thread.join(); 45 | assert_eq!(result.unwrap(), format!("data_n{}", i)); 46 | } 47 | } 48 | ``` 49 | 50 | # Why 51 | I am trying to learn rust by reimplementing a python project that I wrote. 52 | 53 | # What works 54 | * Add 55 | * Set 56 | * Replace 57 | * Get 58 | * Delete 59 | * Increment 60 | * Decrement 61 | * Consistent Hashing 62 | * Threading Support 63 | 64 | ## Trait usage 65 | On all supported functions we use traits to be able to send any type of values to memcached. 66 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Configuration file based on https://raw.githubusercontent.com/BurntSushi/rust-csv/master/appveyor.yml 2 | environment: 3 | matrix: 4 | - TARGET: x86_64-pc-windows-msvc 5 | - TARGET: i686-pc-windows-msvc 6 | - TARGET: i686-pc-windows-gnu 7 | install: 8 | - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" 9 | - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" 10 | - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin 11 | - SET PATH=%PATH%;C:\MinGW\bin 12 | - rustc -V 13 | - cargo -V 14 | 15 | # Got from https://github.com/frerich/clcache/blob/master/appveyor.yml 16 | - appveyor DownloadFile "http://s3.amazonaws.com/downloads.northscale.com/memcached-win64-1.4.4-14.zip" -FileName memcached.zip 17 | - 7z x memcached.zip -y 18 | - ps: $Memcached = Start-Process memcached\memcached.exe -PassThru 19 | 20 | build: false 21 | 22 | test_script: 23 | - cargo build --verbose 24 | - cargo test --verbose 25 | -------------------------------------------------------------------------------- /benches/regression.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | #[macro_use] 5 | extern crate serde_derive; 6 | extern crate serde; 7 | extern crate serde_json; 8 | 9 | extern crate bmemcached; 10 | 11 | use criterion::Criterion; 12 | 13 | use bmemcached::errors::Result; 14 | use bmemcached::protocol::Protocol; 15 | use bmemcached::{FromMemcached, StoredType, ToMemcached}; 16 | 17 | #[derive(Debug, Default, Serialize, Deserialize)] 18 | struct Data { 19 | value: usize, 20 | } 21 | 22 | impl ToMemcached for Data { 23 | fn get_value(&self) -> Result<(Vec, StoredType)> { 24 | Ok(( 25 | serde_json::to_vec(self).unwrap(), 26 | StoredType::MTYPE_USER_DEFINED_1, 27 | )) 28 | } 29 | } 30 | 31 | impl FromMemcached for Data { 32 | fn get_value(flags: StoredType, buf: Vec) -> Result { 33 | assert!(flags == StoredType::MTYPE_USER_DEFINED_1); 34 | Ok(serde_json::from_slice(&*buf).unwrap()) 35 | } 36 | } 37 | 38 | fn criterion_benchmark(c: &mut Criterion) { 39 | c.bench_function("set/get/delete", |b| { 40 | let mut cli = Protocol::connect("127.0.0.1:11211").unwrap(); 41 | b.iter(|| { 42 | let key = "benchmark test"; 43 | cli.set(key, "abc", 10_000).unwrap(); 44 | let returned_value: String = cli.get(key).unwrap(); 45 | assert_eq!(returned_value, "abc"); 46 | cli.delete(key).unwrap(); 47 | }) 48 | }); 49 | 50 | c.bench_function("set/get/delete struct", |b| { 51 | let mut cli = Protocol::connect("127.0.0.1:11211").unwrap(); 52 | b.iter(|| { 53 | let key = "benchmark test"; 54 | let data = Data::default(); 55 | cli.set(key, data, 10_000).unwrap(); 56 | let returned_value: Data = cli.get(key).unwrap(); 57 | assert_eq!(returned_value.value, 0); 58 | cli.delete(key).unwrap(); 59 | }) 60 | }); 61 | } 62 | 63 | criterion_group!(benches, criterion_benchmark); 64 | criterion_main!(benches); 65 | -------------------------------------------------------------------------------- /examples/serialization.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This is an example of how to make bmemcached save your own types and it uses serde for serializing 3 | and deserializing. 4 | */ 5 | extern crate bmemcached; 6 | #[macro_use] extern crate serde_derive; 7 | extern crate serde; 8 | extern crate serde_json; 9 | use bmemcached::{ToMemcached, FromMemcached, StoredType}; 10 | use bmemcached::errors::Result; 11 | 12 | #[derive(Debug, Serialize, Deserialize)] 13 | struct Data { 14 | name: String, 15 | age: u8, 16 | registered: bool 17 | } 18 | 19 | impl<'a> ToMemcached for &'a Data { 20 | fn get_value(&self) -> Result<(Vec, StoredType)> { 21 | Ok((serde_json::to_vec(self).unwrap(), StoredType::MTYPE_USER_DEFINED_1)) 22 | } 23 | } 24 | 25 | impl FromMemcached for Data { 26 | fn get_value(flags: StoredType, buf: Vec) -> Result { 27 | assert!(flags == StoredType::MTYPE_USER_DEFINED_1); 28 | Ok(serde_json::from_slice(&*buf).unwrap()) 29 | } 30 | } 31 | 32 | fn main() { 33 | let data = Data { name: "Testing".to_owned(), age: 8, registered: false }; 34 | let memcached = bmemcached::MemcachedClient::new( 35 | vec!["127.0.0.1:11211"], 5).unwrap(); 36 | println!("Storing {:?}", data); 37 | memcached.set("testing", &data, 10000).unwrap(); 38 | let rv: Vec = memcached.get("testing").unwrap(); 39 | let string = String::from_utf8(rv).unwrap(); 40 | println!("Raw data {:?}", string); 41 | let rv: Data = memcached.get("testing").unwrap(); 42 | println!("Parsed data {:?}", rv); 43 | } 44 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use std::net::ToSocketAddrs; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use conhash::{ConsistentHash, Node}; 5 | 6 | use errors::Result; 7 | use protocol; 8 | 9 | #[derive(Debug, Clone)] 10 | struct ClonableProtocol { 11 | connection: Arc>, 12 | } 13 | 14 | impl Node for ClonableProtocol { 15 | fn name(&self) -> String { 16 | let protocol = self.clone(); 17 | let connection = protocol.connection.lock().unwrap(); 18 | connection.connection_info() 19 | } 20 | } 21 | 22 | /// Struct that holds all connections and proxy commands to the right server based on the key 23 | pub struct MemcachedClient { 24 | connections: ConsistentHash, 25 | } 26 | 27 | impl MemcachedClient { 28 | pub fn new( 29 | addrs: Vec, 30 | connections_per_addr: u8, 31 | ) -> Result { 32 | let mut ch = ConsistentHash::new(); 33 | for addr in &addrs { 34 | for _ in 0..connections_per_addr { 35 | let protocol = protocol::Protocol::connect(addr)?; 36 | ch.add( 37 | &ClonableProtocol { connection: Arc::new(Mutex::new(protocol)) }, 38 | 1, 39 | ); 40 | } 41 | } 42 | Ok(MemcachedClient { connections: ch }) 43 | } 44 | 45 | pub fn set(&self, key: K, value: V, time: u32) -> Result<()> 46 | where 47 | K: AsRef<[u8]>, 48 | V: protocol::ToMemcached, 49 | { 50 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 51 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 52 | protocol.set(key, value, time) 53 | } 54 | 55 | pub fn add(&self, key: K, value: V, time: u32) -> Result<()> 56 | where 57 | K: AsRef<[u8]>, 58 | V: protocol::ToMemcached, 59 | { 60 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 61 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 62 | protocol.add(key, value, time) 63 | } 64 | 65 | pub fn replace(&self, key: K, value: V, time: u32) -> Result<()> 66 | where 67 | K: AsRef<[u8]>, 68 | V: protocol::ToMemcached, 69 | { 70 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 71 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 72 | protocol.replace(key, value, time) 73 | } 74 | 75 | pub fn get(&self, key: K) -> Result 76 | where 77 | K: AsRef<[u8]>, 78 | V: protocol::FromMemcached, 79 | { 80 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 81 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 82 | protocol.get(key) 83 | } 84 | 85 | pub fn delete(&self, key: K) -> Result<()> 86 | where 87 | K: AsRef<[u8]>, 88 | { 89 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 90 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 91 | protocol.delete(key) 92 | } 93 | 94 | pub fn increment( 95 | &self, 96 | key: K, 97 | amount: u64, 98 | initial: u64, 99 | time: u32, 100 | ) -> Result 101 | where 102 | K: AsRef<[u8]>, 103 | { 104 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 105 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 106 | protocol.increment(key, amount, initial, time) 107 | } 108 | 109 | pub fn decrement( 110 | &self, 111 | key: K, 112 | amount: u64, 113 | initial: u64, 114 | time: u32, 115 | ) -> Result 116 | where 117 | K: AsRef<[u8]>, 118 | { 119 | let clonable_protocol = self.connections.get(key.as_ref()).unwrap(); 120 | let mut protocol = clonable_protocol.connection.lock().unwrap(); 121 | protocol.decrement(key, amount, initial, time) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | bitflags! { 2 | pub struct StoredType: u32 { 3 | const MTYPE_STRING = 1 << 0; 4 | const MTYPE_U8 = 1 << 1; 5 | const MTYPE_U16 = 1 << 2; 6 | const MTYPE_U32 = 1 << 3; 7 | const MTYPE_U64 = 1 << 4; 8 | const MTYPE_VECTOR = 1 << 5; 9 | #[allow(dead_code)] 10 | const MTYPE_COMPRESSED = 1 << 6; 11 | #[allow(dead_code)] 12 | const MTYPE_USER_DEFINED_1 = 1 << 10; 13 | #[allow(dead_code)] 14 | const MTYPE_USER_DEFINED_2 = 1 << 11; 15 | #[allow(dead_code)] 16 | const MTYPE_USER_DEFINED_3 = 1 << 13; 17 | #[allow(dead_code)] 18 | const MTYPE_USER_DEFINED_4 = 1 << 14; 19 | #[allow(dead_code)] 20 | const MTYPE_USER_DEFINED_5 = 1 << 15; 21 | #[allow(dead_code)] 22 | const MTYPE_USER_DEFINED_6 = 1 << 16; 23 | #[allow(dead_code)] 24 | const MTYPE_USER_DEFINED_7 = 1 << 17; 25 | #[allow(dead_code)] 26 | const MTYPE_USER_DEFINED_8 = 1 << 18; 27 | #[allow(dead_code)] 28 | const MTYPE_USER_DEFINED_9 = 1 << 19; 29 | #[allow(dead_code)] 30 | const MTYPE_USER_DEFINED_10 = 1 << 20; 31 | #[allow(dead_code)] 32 | const MTYPE_USER_DEFINED_11 = 1 << 21; 33 | #[allow(dead_code)] 34 | const MTYPE_USER_DEFINED_12 = 1 << 22; 35 | #[allow(dead_code)] 36 | const MTYPE_USER_DEFINED_13 = 1 << 23; 37 | #[allow(dead_code)] 38 | const MTYPE_USER_DEFINED_14 = 1 << 24; 39 | #[allow(dead_code)] 40 | const MTYPE_USER_DEFINED_15 = 1 << 25; 41 | #[allow(dead_code)] 42 | const MTYPE_USER_DEFINED_16 = 1 << 26; 43 | #[allow(dead_code)] 44 | const MTYPE_USER_DEFINED_17 = 1 << 27; 45 | #[allow(dead_code)] 46 | const MTYPE_USER_DEFINED_18 = 1 << 28; 47 | #[allow(dead_code)] 48 | const MTYPE_USER_DEFINED_19 = 1 << 29; 49 | #[allow(dead_code)] 50 | const MTYPE_USER_DEFINED_20 = 1 << 30; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use constants::StoredType; 2 | use protocol::{Status, KEY_MAXIMUM_SIZE}; 3 | 4 | error_chain! { 5 | foreign_links { 6 | IoError(::std::io::Error); 7 | Utf8Error(::std::string::FromUtf8Error); 8 | } 9 | 10 | errors { 11 | Status(s: Status) { 12 | description("Invalid status received") 13 | display("Invalid status received {:?}", s) 14 | } 15 | /// In case you tried to coerce to a value that does not match with the stored. 16 | /// The returned flags are inside the error. 17 | TypeMismatch(s: StoredType) { 18 | description("Requested type is different from the one stored in memcached") 19 | display("Requested type is different from the one stored in memcached: {:?}", s) 20 | } 21 | 22 | KeyLengthTooLong(length: usize) { 23 | description("Key length is too long") 24 | display("Key length {} is too long, the maximum is {}", length, KEY_MAXIMUM_SIZE) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This is a binary memcached protocol implemented only in rust with support of traits to send 3 | and receive `T` and consistent hashing to select connections from a pool to distribute data. 4 | # Example 5 | ```rust 6 | extern crate bmemcached; 7 | 8 | use std::sync::Arc; 9 | use std::thread; 10 | 11 | use bmemcached::MemcachedClient; 12 | 13 | fn main() { 14 | // Use arc for threading support 15 | let client = Arc::new(MemcachedClient::new(vec!["127.0.0.1:11211"], 5).unwrap()); 16 | 17 | // Traits examples 18 | let value = "value"; 19 | client.set("string", value, 1000); 20 | let rv: String = client.get("string").unwrap(); 21 | assert_eq!(rv, "value"); 22 | 23 | client.set("integer", 10 as u8, 1000); 24 | let rv: u8 = client.get("integer").unwrap(); 25 | assert_eq!(rv, 10 as u8); 26 | 27 | // Threads example 28 | let mut threads = vec![]; 29 | for i in 0..4 { 30 | let client = client.clone(); 31 | threads.push(thread::spawn(move || { 32 | let data = format!("data_n{}", i); 33 | client.set(&data, &data, 100).unwrap(); 34 | let val: String = client.get(&data).unwrap(); 35 | client.delete(&data).unwrap(); 36 | val 37 | })); 38 | } 39 | for (i, thread) in threads.into_iter().enumerate() { 40 | let result = thread.join(); 41 | assert_eq!(result.unwrap(), format!("data_n{}", i)); 42 | } 43 | } 44 | ``` 45 | */ 46 | #![forbid(unsafe_code)] 47 | #[macro_use] 48 | extern crate bitflags; 49 | extern crate byteorder; 50 | extern crate conhash; 51 | #[macro_use] 52 | extern crate enum_primitive; 53 | #[macro_use] 54 | extern crate log; 55 | extern crate num; 56 | 57 | mod client; 58 | pub mod constants; 59 | pub mod errors; 60 | pub mod protocol; 61 | 62 | pub use protocol::{FromMemcached, Status, ToMemcached}; 63 | #[macro_use] 64 | extern crate error_chain; 65 | 66 | pub use client::MemcachedClient; 67 | pub use constants::StoredType; 68 | -------------------------------------------------------------------------------- /src/protocol.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufReader, BufWriter, Cursor, Read, Write}; 2 | use std::net::{TcpStream, ToSocketAddrs}; 3 | 4 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 5 | use enum_primitive::FromPrimitive; 6 | 7 | use constants::*; 8 | use errors::{ErrorKind, Result}; 9 | 10 | pub const KEY_MAXIMUM_SIZE: usize = 250; 11 | 12 | enum Type { 13 | Request = 0x80, 14 | Response = 0x81, 15 | } 16 | 17 | #[derive(Debug)] 18 | enum Command { 19 | Get = 0x00, 20 | Set = 0x01, 21 | Add = 0x02, 22 | Replace = 0x03, 23 | Delete = 0x04, 24 | Increment = 0x05, 25 | Decrement = 0x06, 26 | // Quit = 0x07, 27 | // Flush = 0x08, 28 | // GetQ = 0x09, 29 | // NoOp = 0x0A, 30 | // Version = 0x0B, 31 | // GetK = 0x0C, 32 | // GetKQ = 0x0D, 33 | // Append = 0x0E, 34 | // Prepend = 0x0F, 35 | // Stat = 0x10, 36 | // SetQ = 0x11, 37 | // AddQ = 0x12, 38 | // ReplaceQ = 0x13, 39 | // DeleteQ = 0x14, 40 | // IncrementQ = 0x15, 41 | // DecrementQ = 0x16, 42 | // QuitQ = 0x17, 43 | // FlushQ = 0x18, 44 | // AppendQ = 0x19, 45 | // PrependQ = 0x1A 46 | } 47 | 48 | enum_from_primitive! { 49 | #[derive(Debug, PartialEq)] 50 | pub enum Status { 51 | Success = 0x00, 52 | KeyNotFound = 0x01, 53 | KeyExists = 0x02, 54 | ValueTooBig = 0x03, 55 | InvalidArguments = 0x04, 56 | AuthError = 0x08, 57 | UnknownCommand = 0x81 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct Request { 63 | magic: u8, 64 | opcode: u8, 65 | key_length: u16, 66 | extras_length: u8, 67 | data_type: u8, 68 | reserved: u16, 69 | body_length: u32, 70 | opaque: u32, 71 | cas: u64, 72 | } 73 | 74 | #[derive(Debug)] 75 | pub struct Response { 76 | magic: u8, 77 | opcode: u8, 78 | key_length: u16, 79 | extras_length: u8, 80 | data_type: u8, 81 | status: u16, 82 | body_length: u32, 83 | opaque: u32, 84 | cas: u64, 85 | } 86 | 87 | #[derive(Debug)] 88 | pub struct Protocol { 89 | connection: BufReader, 90 | } 91 | 92 | pub trait ToMemcached { 93 | fn get_value(&self) -> Result<(Vec, StoredType)>; 94 | } 95 | 96 | pub trait FromMemcached: Sized { 97 | fn get_value(flags: StoredType, buf: Vec) -> Result; 98 | } 99 | 100 | impl Protocol { 101 | pub fn connect(addr: A) -> Result { 102 | Ok(Protocol { 103 | connection: BufReader::new(TcpStream::connect(addr)?), 104 | }) 105 | } 106 | 107 | pub fn connection_info(&self) -> String { 108 | let connection = self.connection.get_ref(); 109 | connection.peer_addr().unwrap().to_string() 110 | } 111 | 112 | fn build_request( 113 | command: Command, 114 | key_length: usize, 115 | value_length: usize, 116 | data_type: u8, 117 | extras_length: usize, 118 | cas: u64, 119 | ) -> Result { 120 | if key_length > KEY_MAXIMUM_SIZE { 121 | bail!(ErrorKind::KeyLengthTooLong(key_length)); 122 | } 123 | Ok(Request { 124 | magic: Type::Request as u8, 125 | opcode: command as u8, 126 | key_length: key_length as u16, 127 | extras_length: extras_length as u8, 128 | data_type: data_type, 129 | reserved: 0, 130 | body_length: (key_length + value_length + extras_length) as u32, 131 | opaque: 0, 132 | cas: cas, 133 | }) 134 | } 135 | 136 | fn write_request(&mut self, request: Request, final_payload: &[u8]) -> Result<()> { 137 | let connection = self.connection.get_mut(); 138 | let mut buf = BufWriter::new(connection); 139 | buf.write_u8(request.magic)?; 140 | buf.write_u8(request.opcode)?; 141 | buf.write_u16::(request.key_length)?; 142 | buf.write_u8(request.extras_length)?; 143 | buf.write_u8(request.data_type)?; 144 | buf.write_u16::(request.reserved)?; 145 | buf.write_u32::(request.body_length)?; 146 | buf.write_u32::(request.opaque)?; 147 | buf.write_u64::(request.cas)?; 148 | buf.write(final_payload)?; 149 | buf.flush()?; 150 | Ok(()) 151 | } 152 | 153 | fn read_response(&mut self) -> Result { 154 | let buf = &mut self.connection; 155 | let magic: u8 = buf.read_u8()?; 156 | if magic != Type::Response as u8 { 157 | // TODO Consume the stream, disconnect or something? 158 | debug!("Server sent an unknown magic code {:?}", magic); 159 | bail!("Server sent an unknown magic code"); 160 | } 161 | Ok(Response { 162 | magic, 163 | opcode: buf.read_u8()?, 164 | key_length: buf.read_u16::()?, 165 | extras_length: buf.read_u8()?, 166 | data_type: buf.read_u8()?, 167 | status: buf.read_u16::()?, 168 | body_length: buf.read_u32::()?, 169 | opaque: buf.read_u32::()?, 170 | cas: buf.read_u64::()?, 171 | }) 172 | } 173 | 174 | fn consume_body(&mut self, size: u32) -> Result<()> { 175 | debug!("Consuming body"); 176 | let mut buf: Vec = vec![0; size as usize]; 177 | self.connection.read(&mut *buf)?; 178 | let str_buf = String::from_utf8(buf)?; 179 | debug!("Consumed body {:?}", str_buf); 180 | Ok(()) 181 | } 182 | 183 | fn set_add_replace(&mut self, command: Command, key: K, value: V, time: u32) -> Result<()> 184 | where 185 | K: AsRef<[u8]>, 186 | V: ToMemcached, 187 | { 188 | let key = key.as_ref(); 189 | let (value, flags) = value.get_value()?; 190 | 191 | let extras_length = 8; // Flags: u32 and Expiration time: u32 192 | let request = 193 | Protocol::build_request(command, key.len(), value.len(), 0x00, extras_length, 0x00)?; 194 | let mut final_payload = vec![]; 195 | // Flags 196 | final_payload.write_u32::(flags.bits())?; 197 | final_payload.write_u32::(time)?; 198 | // After flags key and value 199 | final_payload.write(key)?; 200 | final_payload.write(&value)?; 201 | self.write_request(request, final_payload.as_slice())?; 202 | let response = self.read_response()?; 203 | match Status::from_u16(response.status) { 204 | Some(Status::Success) => Ok(()), 205 | Some(rest) => { 206 | self.consume_body(response.body_length)?; 207 | bail!(ErrorKind::Status(rest)) 208 | } 209 | None => bail!( 210 | "Server returned an unknown status code 0x{:02x}", 211 | response.status 212 | ), 213 | } 214 | } 215 | 216 | pub fn set(&mut self, key: K, value: V, time: u32) -> Result<()> 217 | where 218 | K: AsRef<[u8]>, 219 | V: ToMemcached, 220 | { 221 | self.set_add_replace(Command::Set, key, value, time) 222 | } 223 | 224 | pub fn add(&mut self, key: K, value: V, time: u32) -> Result<()> 225 | where 226 | K: AsRef<[u8]>, 227 | V: ToMemcached, 228 | { 229 | self.set_add_replace(Command::Add, key, value, time) 230 | } 231 | 232 | pub fn replace(&mut self, key: K, value: V, time: u32) -> Result<()> 233 | where 234 | K: AsRef<[u8]>, 235 | V: ToMemcached, 236 | { 237 | self.set_add_replace(Command::Replace, key, value, time) 238 | } 239 | 240 | pub fn get(&mut self, key: K) -> Result 241 | where 242 | K: AsRef<[u8]>, 243 | V: FromMemcached, 244 | { 245 | let key = key.as_ref(); 246 | let request = Protocol::build_request(Command::Get, key.len(), 0 as usize, 0, 0, 0x00)?; 247 | self.write_request(request, key)?; 248 | let response = self.read_response()?; 249 | match Status::from_u16(response.status) { 250 | Some(Status::Success) => {} 251 | Some(status) => { 252 | self.consume_body(response.body_length)?; 253 | bail!(ErrorKind::Status(status)); 254 | } 255 | None => { 256 | bail!( 257 | "Server sent an unknown status code 0x{:02x}", 258 | response.status 259 | ); 260 | } 261 | }; 262 | let flags = StoredType::from_bits(self.connection.read_u32::()?).unwrap(); 263 | let mut outbuf = vec![0; (response.body_length - response.extras_length as u32) as usize]; 264 | self.connection.read_exact(&mut outbuf)?; 265 | FromMemcached::get_value(flags, outbuf) 266 | } 267 | 268 | pub fn delete(&mut self, key: K) -> Result<()> 269 | where 270 | K: AsRef<[u8]>, 271 | { 272 | let key = key.as_ref(); 273 | let request = Protocol::build_request(Command::Delete, key.len(), 0 as usize, 0, 0, 0x00)?; 274 | self.write_request(request, key)?; 275 | let response = self.read_response()?; 276 | 277 | match Status::from_u16(response.status) { 278 | Some(Status::Success) => Ok(()), 279 | Some(Status::KeyNotFound) => { 280 | self.consume_body(response.body_length)?; 281 | Ok(()) 282 | } 283 | Some(status) => { 284 | self.consume_body(response.body_length)?; 285 | bail!(ErrorKind::Status(status)) 286 | } 287 | None => bail!( 288 | "Server sent an unknown status code 0x{:02x}", 289 | response.status 290 | ), 291 | } 292 | } 293 | 294 | fn increment_decrement( 295 | &mut self, 296 | key: K, 297 | amount: u64, 298 | initial: u64, 299 | time: u32, 300 | command: Command, 301 | ) -> Result 302 | where 303 | K: AsRef<[u8]>, 304 | { 305 | let key = key.as_ref(); 306 | let extras_length = 20; // Amount: u64, Initial: u64, Time: u32 307 | let request = Protocol::build_request(command, key.len(), 0, 0, extras_length, 0x00)?; 308 | let mut final_payload: Vec = vec![]; 309 | final_payload.write_u64::(amount)?; 310 | final_payload.write_u64::(initial)?; 311 | final_payload.write_u32::(time)?; 312 | final_payload.write(key)?; 313 | self.write_request(request, &final_payload)?; 314 | let response = self.read_response()?; 315 | match Status::from_u16(response.status) { 316 | Some(Status::Success) => Ok(self.connection.read_u64::()?), 317 | Some(status) => { 318 | self.consume_body(response.body_length)?; 319 | bail!(ErrorKind::Status(status)) 320 | } 321 | None => bail!("Server sent an unknown status code"), 322 | } 323 | } 324 | 325 | pub fn increment(&mut self, key: K, amount: u64, initial: u64, time: u32) -> Result 326 | where 327 | K: AsRef<[u8]>, 328 | { 329 | self.increment_decrement(key, amount, initial, time, Command::Increment) 330 | } 331 | 332 | pub fn decrement(&mut self, key: K, amount: u64, initial: u64, time: u32) -> Result 333 | where 334 | K: AsRef<[u8]>, 335 | { 336 | self.increment_decrement(key, amount, initial, time, Command::Decrement) 337 | } 338 | } 339 | 340 | impl ToMemcached for u8 { 341 | fn get_value(&self) -> Result<(Vec, StoredType)> { 342 | Ok((vec![*self], StoredType::MTYPE_U8)) 343 | } 344 | } 345 | 346 | impl ToMemcached for u16 { 347 | fn get_value(&self) -> Result<(Vec, StoredType)> { 348 | let mut buf = vec![]; 349 | buf.write_u16::(*self)?; 350 | Ok((buf, StoredType::MTYPE_U16)) 351 | } 352 | } 353 | 354 | impl ToMemcached for u32 { 355 | fn get_value(&self) -> Result<(Vec, StoredType)> { 356 | let mut buf = vec![]; 357 | buf.write_u32::(*self)?; 358 | Ok((buf, StoredType::MTYPE_U32)) 359 | } 360 | } 361 | 362 | impl ToMemcached for u64 { 363 | fn get_value(&self) -> Result<(Vec, StoredType)> { 364 | let mut buf = vec![]; 365 | buf.write_u64::(*self)?; 366 | Ok((buf, StoredType::MTYPE_U64)) 367 | } 368 | } 369 | 370 | impl<'a> ToMemcached for &'a String { 371 | fn get_value(&self) -> Result<(Vec, StoredType)> { 372 | let v = *self; 373 | Ok((v.clone().into_bytes(), StoredType::MTYPE_STRING)) 374 | } 375 | } 376 | 377 | impl<'a> ToMemcached for &'a str { 378 | fn get_value(&self) -> Result<(Vec, StoredType)> { 379 | Ok((self.as_bytes().to_vec(), StoredType::MTYPE_STRING)) 380 | } 381 | } 382 | 383 | impl<'a> ToMemcached for &'a [u8] { 384 | fn get_value(&self) -> Result<(Vec, StoredType)> { 385 | Ok((self.to_vec(), StoredType::MTYPE_VECTOR)) 386 | } 387 | } 388 | 389 | impl FromMemcached for String { 390 | fn get_value(flags: StoredType, buf: Vec) -> Result { 391 | if flags & StoredType::MTYPE_STRING != StoredType::empty() { 392 | Ok(String::from_utf8(buf)?) 393 | } else { 394 | bail!(ErrorKind::TypeMismatch(flags)) 395 | } 396 | } 397 | } 398 | 399 | impl FromMemcached for u8 { 400 | fn get_value(flags: StoredType, buf: Vec) -> Result { 401 | if flags & StoredType::MTYPE_U8 != StoredType::empty() { 402 | let mut buf = Cursor::new(buf); 403 | Ok(buf.read_u8()?) 404 | } else { 405 | bail!(ErrorKind::TypeMismatch(flags)) 406 | } 407 | } 408 | } 409 | 410 | impl FromMemcached for u16 { 411 | fn get_value(flags: StoredType, buf: Vec) -> Result { 412 | if flags & StoredType::MTYPE_U16 != StoredType::empty() { 413 | let mut buf = Cursor::new(buf); 414 | Ok(buf.read_u16::()?) 415 | } else { 416 | bail!(ErrorKind::TypeMismatch(flags)) 417 | } 418 | } 419 | } 420 | 421 | impl FromMemcached for u32 { 422 | fn get_value(flags: StoredType, buf: Vec) -> Result { 423 | if flags & StoredType::MTYPE_U32 != StoredType::empty() { 424 | let mut buf = Cursor::new(buf); 425 | Ok(buf.read_u32::()?) 426 | } else { 427 | bail!(ErrorKind::TypeMismatch(flags)) 428 | } 429 | } 430 | } 431 | 432 | impl FromMemcached for u64 { 433 | #[allow(unused_variables)] 434 | fn get_value(flags: StoredType, buf: Vec) -> Result { 435 | // As increment and decrement don't allow us to send flags, we don't 436 | // enforce type checking. 437 | let mut buf = Cursor::new(buf); 438 | Ok(buf.read_u64::()?) 439 | } 440 | } 441 | 442 | impl FromMemcached for Vec { 443 | #[allow(unused_variables)] 444 | fn get_value(flags: StoredType, buf: Vec) -> Result { 445 | Ok(buf) 446 | } 447 | } 448 | 449 | #[cfg(test)] 450 | mod tests { 451 | extern crate env_logger; 452 | 453 | use std::iter; 454 | 455 | use super::*; 456 | use errors::{Error, Result}; 457 | 458 | #[test] 459 | fn set() { 460 | let _ = env_logger::try_init(); 461 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 462 | let key = "Hello Set"; 463 | let value = "World"; 464 | p.set(key, value, 1000).unwrap(); 465 | p.delete(key).unwrap(); 466 | let data: String = iter::repeat("0").take(1024 * 1024).collect(); 467 | let err = p.set("big-data", &data, 100_000).unwrap_err(); 468 | match err.kind() { 469 | &ErrorKind::Status(Status::ValueTooBig) => {} 470 | e => panic!("Value should not be {:?}", e), 471 | } 472 | } 473 | 474 | #[test] 475 | fn set_u8() { 476 | let _ = env_logger::try_init(); 477 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 478 | let key = "Hello"; 479 | let value = 1 as u8; 480 | p.set(key, value, 1000).unwrap(); 481 | p.delete(key).unwrap(); 482 | } 483 | 484 | #[test] 485 | fn set_u16() { 486 | let _ = env_logger::try_init(); 487 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 488 | let key = "Hello"; 489 | let value = 1 as u16; 490 | p.set(key, value, 1000).unwrap(); 491 | p.delete(key).unwrap(); 492 | } 493 | 494 | #[test] 495 | fn set_u32() { 496 | let _ = env_logger::try_init(); 497 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 498 | let key = "Hello"; 499 | let value = 1 as u32; 500 | p.set(key, value, 100).unwrap(); 501 | p.delete(key).unwrap(); 502 | } 503 | 504 | #[test] 505 | fn set_u64() { 506 | let _ = env_logger::try_init(); 507 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 508 | let key = "Hello"; 509 | let value = 1 as u64; 510 | p.set(key, value, 1000).unwrap(); 511 | p.delete(key).unwrap(); 512 | } 513 | 514 | #[test] 515 | fn set_slice() { 516 | let _ = env_logger::try_init(); 517 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 518 | let key = "Hello"; 519 | let value = vec![1, 2, 3]; 520 | p.set(key, &value[..], 1000).unwrap(); 521 | p.delete(key).unwrap(); 522 | } 523 | 524 | #[test] 525 | fn add_key() { 526 | let _ = env_logger::try_init(); 527 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 528 | let key = "Hello Add"; 529 | let value = "World"; 530 | p.add(key, value, 10).unwrap(); 531 | let result = p.add(key, value, 10); 532 | match result { 533 | Ok(()) => panic!("Add key should return error"), 534 | Err(Error(ErrorKind::Status(Status::KeyExists), _)) => {} 535 | Err(_) => panic!("Some strange error that should not happen"), 536 | }; 537 | p.delete(key).unwrap(); 538 | } 539 | 540 | #[test] 541 | fn get_key() { 542 | let _ = env_logger::try_init(); 543 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 544 | let key = "Hello Get"; 545 | let value = "World"; 546 | p.set(key, value, 10000).unwrap(); 547 | let rv: String = p.get(key).unwrap(); 548 | assert_eq!(rv, value); 549 | 550 | let not_found: Result = p.get("not found".to_string()); 551 | match not_found { 552 | Ok(_) => panic!("This key should not exist"), 553 | Err(Error(ErrorKind::Status(Status::KeyNotFound), _)) => {} 554 | Err(_) => panic!("This should return KeyNotFound"), 555 | }; 556 | p.delete(key).unwrap(); 557 | let big_key: String = iter::repeat("0").take(260).collect(); 558 | match p.get::<_, Vec>(big_key) { 559 | Ok(_) => panic!("Should be an error"), 560 | Err(Error(ErrorKind::KeyLengthTooLong(260), _)) => {} 561 | Err(e) => panic!("This should be KeyLengthTooLong and not {:?}", e), 562 | }; 563 | } 564 | 565 | #[test] 566 | fn delete_key() { 567 | let _ = env_logger::try_init(); 568 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 569 | let key = "Hello Delete"; 570 | let value = "World"; 571 | p.set(key, value, 1000).unwrap(); 572 | p.delete(key).unwrap(); 573 | p.delete(key).unwrap(); 574 | } 575 | 576 | #[test] 577 | fn increment() { 578 | let _ = env_logger::try_init(); 579 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 580 | let key = "Hello Increment"; 581 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 0); 582 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 1); 583 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 2); 584 | p.delete(key).unwrap(); 585 | } 586 | 587 | #[test] 588 | fn decrement() { 589 | let _ = env_logger::try_init(); 590 | let mut p = Protocol::connect("127.0.0.1:11211").unwrap(); 591 | let key = "Hello Decrement"; 592 | assert_eq!(p.decrement(key, 1, 0, 1000).unwrap(), 0); 593 | assert_eq!(p.decrement(key, 1, 0, 1000).unwrap(), 0); 594 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 1); 595 | assert_eq!(p.increment(key, 1, 0, 1000).unwrap(), 2); 596 | assert_eq!(p.decrement(key, 1, 0, 1000).unwrap(), 1); 597 | p.delete(key).unwrap(); 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /tests/client.rs: -------------------------------------------------------------------------------- 1 | extern crate env_logger; 2 | #[macro_use] 3 | extern crate log; 4 | extern crate bmemcached; 5 | 6 | use std::sync::Arc; 7 | use std::thread; 8 | 9 | use bmemcached::errors::{Error, ErrorKind}; 10 | use bmemcached::{MemcachedClient, Status}; 11 | 12 | #[test] 13 | fn multiple_threads() { 14 | let _ = env_logger::try_init(); 15 | let mut threads = vec![]; 16 | let client = Arc::new(MemcachedClient::new(vec!["127.0.0.1:11211"], 5).unwrap()); 17 | for i in 0..4 { 18 | let client = client.clone(); 19 | debug!("Starting thread {}", i); 20 | threads.push(thread::spawn(move || { 21 | debug!("Started {}", i); 22 | let data = format!("data_n{}", i); 23 | client.set(&data, &data, 100).unwrap(); 24 | let val: String = client.get(&data).unwrap(); 25 | client.delete(&data).unwrap(); 26 | debug!("Finished {}", i); 27 | val 28 | })); 29 | } 30 | for (i, thread) in threads.into_iter().enumerate() { 31 | let result = thread.join(); 32 | assert_eq!(result.unwrap(), format!("data_n{}", i)); 33 | } 34 | } 35 | 36 | #[test] 37 | fn get_set_delete() { 38 | let _ = env_logger::try_init(); 39 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 40 | let key = "Hello Get, Set, Delete Client"; 41 | let value = "World"; 42 | client.set(key, value, 1000).unwrap(); 43 | let rv: String = client.get(key).unwrap(); 44 | assert_eq!(rv, value); 45 | client.delete(key).unwrap(); 46 | } 47 | 48 | #[test] 49 | fn get_set_u8() { 50 | let _ = env_logger::try_init(); 51 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 52 | let key = "Hello u8"; 53 | let value = 1 as u8; 54 | client.set(key, value, 1000).unwrap(); 55 | 56 | let rv: u8 = client.get(key).unwrap(); 57 | assert_eq!(rv, value); 58 | client.delete(key).unwrap(); 59 | } 60 | 61 | #[test] 62 | fn get_set_u16() { 63 | let _ = env_logger::try_init(); 64 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 65 | let key = "Hello u16"; 66 | let value = 1 as u16; 67 | client.set(key, value, 1000).unwrap(); 68 | 69 | let rv: u16 = client.get(key).unwrap(); 70 | assert_eq!(rv, value); 71 | client.delete(key).unwrap(); 72 | } 73 | 74 | #[test] 75 | fn get_set_u32() { 76 | let _ = env_logger::try_init(); 77 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 78 | let key = "Hello u32"; 79 | let value = 1 as u32; 80 | client.set(key, value, 1000).unwrap(); 81 | 82 | let rv: u32 = client.get(key).unwrap(); 83 | assert_eq!(rv, value); 84 | client.delete(key).unwrap(); 85 | } 86 | 87 | #[test] 88 | fn get_set_u64() { 89 | let _ = env_logger::try_init(); 90 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 91 | let key = "Hello u64"; 92 | let value = 1 as u64; 93 | client.set(key, value, 1000).unwrap(); 94 | 95 | let rv: u64 = client.get(key).unwrap(); 96 | assert_eq!(rv, value); 97 | client.delete(key).unwrap(); 98 | } 99 | 100 | #[test] 101 | fn add() { 102 | let _ = env_logger::try_init(); 103 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 104 | let key = "Hello Add Client"; 105 | let value = "World"; 106 | client.add(key, value, 1000).unwrap(); 107 | let rv: String = client.get(key).unwrap(); 108 | assert_eq!(rv, value); 109 | match client.add(key, value, 1000) { 110 | Err(Error(ErrorKind::Status(Status::KeyExists), _)) => (), 111 | e => panic!("Wrong status returned {:?}", e), 112 | } 113 | client.delete(key).unwrap(); 114 | } 115 | 116 | #[test] 117 | fn replace() { 118 | let _ = env_logger::try_init(); 119 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 120 | let key = "Hello Replace Client"; 121 | let value = "World"; 122 | client.add(key, value, 1000).unwrap(); 123 | 124 | let rv: String = client.get(key).unwrap(); 125 | assert_eq!(rv, value); 126 | 127 | client.replace(key, "New value", 100).unwrap(); 128 | let rv: String = client.get(key).unwrap(); 129 | assert_eq!(rv, "New value"); 130 | client.delete(key).unwrap(); 131 | } 132 | 133 | #[test] 134 | fn increment() { 135 | let _ = env_logger::try_init(); 136 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 137 | let key = "Hello Increment Client"; 138 | assert_eq!(client.increment(key, 1, 0, 1000).unwrap(), 0); 139 | assert_eq!(client.increment(key, 1, 1, 1000).unwrap(), 1); 140 | client.delete(key).unwrap(); 141 | } 142 | 143 | #[test] 144 | fn decrement() { 145 | let _ = env_logger::try_init(); 146 | let client = MemcachedClient::new(vec!["127.0.0.1:11211"], 1).unwrap(); 147 | let key = "Hello Decrement Client"; 148 | assert_eq!(client.decrement(key, 1, 10, 1000).unwrap(), 10); 149 | assert_eq!(client.decrement(key, 1, 1, 1000).unwrap(), 9); 150 | client.delete(key).unwrap(); 151 | } 152 | --------------------------------------------------------------------------------