├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── chat.rs ├── hello_world.rs ├── pub.rs └── sub.rs ├── src ├── bin │ ├── cli.rs │ └── server.rs ├── clients │ ├── blocking_client.rs │ ├── buffered_client.rs │ ├── client.rs │ └── mod.rs ├── cmd │ ├── get.rs │ ├── mod.rs │ ├── ping.rs │ ├── publish.rs │ ├── set.rs │ ├── subscribe.rs │ └── unknown.rs ├── connection.rs ├── db.rs ├── frame.rs ├── lib.rs ├── parse.rs ├── server.rs └── shutdown.rs └── tests ├── buffered_client.rs ├── client.rs └── server.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Build with OTel feature 19 | run: cargo build --verbose --features otel 20 | - name: Run tests 21 | run: cargo test --verbose 22 | - name: Run tests with OTel feature 23 | run: cargo test --verbose --features otel 24 | - name: rustfmt 25 | run: cargo fmt --all --check 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.0.5" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle" 45 | version = "1.0.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" 48 | 49 | [[package]] 50 | name = "anstyle-parse" 51 | version = "0.2.1" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 54 | dependencies = [ 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-query" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 63 | dependencies = [ 64 | "windows-sys", 65 | ] 66 | 67 | [[package]] 68 | name = "anstyle-wincon" 69 | version = "2.1.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" 72 | dependencies = [ 73 | "anstyle", 74 | "windows-sys", 75 | ] 76 | 77 | [[package]] 78 | name = "anyhow" 79 | version = "1.0.75" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 82 | 83 | [[package]] 84 | name = "async-stream" 85 | version = "0.3.5" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" 88 | dependencies = [ 89 | "async-stream-impl", 90 | "futures-core", 91 | "pin-project-lite", 92 | ] 93 | 94 | [[package]] 95 | name = "async-stream-impl" 96 | version = "0.3.5" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" 99 | dependencies = [ 100 | "proc-macro2", 101 | "quote", 102 | "syn 2.0.32", 103 | ] 104 | 105 | [[package]] 106 | name = "async-trait" 107 | version = "0.1.73" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" 110 | dependencies = [ 111 | "proc-macro2", 112 | "quote", 113 | "syn 2.0.32", 114 | ] 115 | 116 | [[package]] 117 | name = "atoi" 118 | version = "2.0.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 121 | dependencies = [ 122 | "num-traits", 123 | ] 124 | 125 | [[package]] 126 | name = "autocfg" 127 | version = "1.1.0" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 130 | 131 | [[package]] 132 | name = "axum" 133 | version = "0.6.20" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" 136 | dependencies = [ 137 | "async-trait", 138 | "axum-core", 139 | "bitflags", 140 | "bytes", 141 | "futures-util", 142 | "http", 143 | "http-body", 144 | "hyper", 145 | "itoa", 146 | "matchit", 147 | "memchr", 148 | "mime", 149 | "percent-encoding", 150 | "pin-project-lite", 151 | "rustversion", 152 | "serde", 153 | "sync_wrapper", 154 | "tower", 155 | "tower-layer", 156 | "tower-service", 157 | ] 158 | 159 | [[package]] 160 | name = "axum-core" 161 | version = "0.3.4" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" 164 | dependencies = [ 165 | "async-trait", 166 | "bytes", 167 | "futures-util", 168 | "http", 169 | "http-body", 170 | "mime", 171 | "rustversion", 172 | "tower-layer", 173 | "tower-service", 174 | ] 175 | 176 | [[package]] 177 | name = "backtrace" 178 | version = "0.3.69" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 181 | dependencies = [ 182 | "addr2line", 183 | "cc", 184 | "cfg-if", 185 | "libc", 186 | "miniz_oxide", 187 | "object", 188 | "rustc-demangle", 189 | ] 190 | 191 | [[package]] 192 | name = "base64" 193 | version = "0.21.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" 196 | 197 | [[package]] 198 | name = "bitflags" 199 | version = "1.3.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 202 | 203 | [[package]] 204 | name = "bumpalo" 205 | version = "3.13.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" 208 | 209 | [[package]] 210 | name = "bytes" 211 | version = "1.5.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 214 | 215 | [[package]] 216 | name = "cc" 217 | version = "1.0.83" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 220 | dependencies = [ 221 | "libc", 222 | ] 223 | 224 | [[package]] 225 | name = "cfg-if" 226 | version = "1.0.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 229 | 230 | [[package]] 231 | name = "clap" 232 | version = "4.4.2" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" 235 | dependencies = [ 236 | "clap_builder", 237 | "clap_derive", 238 | ] 239 | 240 | [[package]] 241 | name = "clap_builder" 242 | version = "4.4.2" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" 245 | dependencies = [ 246 | "anstream", 247 | "anstyle", 248 | "clap_lex", 249 | "strsim", 250 | ] 251 | 252 | [[package]] 253 | name = "clap_derive" 254 | version = "4.4.2" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" 257 | dependencies = [ 258 | "heck", 259 | "proc-macro2", 260 | "quote", 261 | "syn 2.0.32", 262 | ] 263 | 264 | [[package]] 265 | name = "clap_lex" 266 | version = "0.5.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" 269 | 270 | [[package]] 271 | name = "colorchoice" 272 | version = "1.0.0" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 275 | 276 | [[package]] 277 | name = "crossbeam-channel" 278 | version = "0.5.8" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 281 | dependencies = [ 282 | "cfg-if", 283 | "crossbeam-utils", 284 | ] 285 | 286 | [[package]] 287 | name = "crossbeam-utils" 288 | version = "0.8.16" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 291 | dependencies = [ 292 | "cfg-if", 293 | ] 294 | 295 | [[package]] 296 | name = "either" 297 | version = "1.9.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" 300 | 301 | [[package]] 302 | name = "fnv" 303 | version = "1.0.7" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 306 | 307 | [[package]] 308 | name = "futures-channel" 309 | version = "0.3.28" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 312 | dependencies = [ 313 | "futures-core", 314 | ] 315 | 316 | [[package]] 317 | name = "futures-core" 318 | version = "0.3.28" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 321 | 322 | [[package]] 323 | name = "futures-executor" 324 | version = "0.3.28" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 327 | dependencies = [ 328 | "futures-core", 329 | "futures-task", 330 | "futures-util", 331 | ] 332 | 333 | [[package]] 334 | name = "futures-macro" 335 | version = "0.3.28" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 338 | dependencies = [ 339 | "proc-macro2", 340 | "quote", 341 | "syn 2.0.32", 342 | ] 343 | 344 | [[package]] 345 | name = "futures-sink" 346 | version = "0.3.28" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 349 | 350 | [[package]] 351 | name = "futures-task" 352 | version = "0.3.28" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 355 | 356 | [[package]] 357 | name = "futures-util" 358 | version = "0.3.28" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 361 | dependencies = [ 362 | "futures-core", 363 | "futures-macro", 364 | "futures-sink", 365 | "futures-task", 366 | "pin-project-lite", 367 | "pin-utils", 368 | "slab", 369 | ] 370 | 371 | [[package]] 372 | name = "getrandom" 373 | version = "0.2.10" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 376 | dependencies = [ 377 | "cfg-if", 378 | "libc", 379 | "wasi", 380 | ] 381 | 382 | [[package]] 383 | name = "gimli" 384 | version = "0.28.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" 387 | 388 | [[package]] 389 | name = "h2" 390 | version = "0.3.21" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" 393 | dependencies = [ 394 | "bytes", 395 | "fnv", 396 | "futures-core", 397 | "futures-sink", 398 | "futures-util", 399 | "http", 400 | "indexmap", 401 | "slab", 402 | "tokio", 403 | "tokio-util", 404 | "tracing", 405 | ] 406 | 407 | [[package]] 408 | name = "hashbrown" 409 | version = "0.12.3" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 412 | 413 | [[package]] 414 | name = "heck" 415 | version = "0.4.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 418 | 419 | [[package]] 420 | name = "hermit-abi" 421 | version = "0.3.2" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 424 | 425 | [[package]] 426 | name = "http" 427 | version = "0.2.9" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 430 | dependencies = [ 431 | "bytes", 432 | "fnv", 433 | "itoa", 434 | ] 435 | 436 | [[package]] 437 | name = "http-body" 438 | version = "0.4.5" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 441 | dependencies = [ 442 | "bytes", 443 | "http", 444 | "pin-project-lite", 445 | ] 446 | 447 | [[package]] 448 | name = "httparse" 449 | version = "1.8.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 452 | 453 | [[package]] 454 | name = "httpdate" 455 | version = "1.0.3" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 458 | 459 | [[package]] 460 | name = "hyper" 461 | version = "0.14.27" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 464 | dependencies = [ 465 | "bytes", 466 | "futures-channel", 467 | "futures-core", 468 | "futures-util", 469 | "h2", 470 | "http", 471 | "http-body", 472 | "httparse", 473 | "httpdate", 474 | "itoa", 475 | "pin-project-lite", 476 | "socket2 0.4.9", 477 | "tokio", 478 | "tower-service", 479 | "tracing", 480 | "want", 481 | ] 482 | 483 | [[package]] 484 | name = "hyper-timeout" 485 | version = "0.4.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" 488 | dependencies = [ 489 | "hyper", 490 | "pin-project-lite", 491 | "tokio", 492 | "tokio-io-timeout", 493 | ] 494 | 495 | [[package]] 496 | name = "indexmap" 497 | version = "1.9.3" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 500 | dependencies = [ 501 | "autocfg", 502 | "hashbrown", 503 | ] 504 | 505 | [[package]] 506 | name = "itertools" 507 | version = "0.10.5" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 510 | dependencies = [ 511 | "either", 512 | ] 513 | 514 | [[package]] 515 | name = "itoa" 516 | version = "1.0.9" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 519 | 520 | [[package]] 521 | name = "js-sys" 522 | version = "0.3.64" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" 525 | dependencies = [ 526 | "wasm-bindgen", 527 | ] 528 | 529 | [[package]] 530 | name = "lazy_static" 531 | version = "1.4.0" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 534 | 535 | [[package]] 536 | name = "libc" 537 | version = "0.2.153" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 540 | 541 | [[package]] 542 | name = "lock_api" 543 | version = "0.4.10" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 546 | dependencies = [ 547 | "autocfg", 548 | "scopeguard", 549 | ] 550 | 551 | [[package]] 552 | name = "log" 553 | version = "0.4.20" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 556 | 557 | [[package]] 558 | name = "matchers" 559 | version = "0.1.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 562 | dependencies = [ 563 | "regex-automata 0.1.10", 564 | ] 565 | 566 | [[package]] 567 | name = "matchit" 568 | version = "0.7.2" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" 571 | 572 | [[package]] 573 | name = "memchr" 574 | version = "2.6.3" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" 577 | 578 | [[package]] 579 | name = "mime" 580 | version = "0.3.17" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 583 | 584 | [[package]] 585 | name = "mini-redis" 586 | version = "0.4.1" 587 | dependencies = [ 588 | "async-stream", 589 | "atoi", 590 | "bytes", 591 | "clap", 592 | "opentelemetry", 593 | "opentelemetry-aws", 594 | "opentelemetry-otlp", 595 | "tokio", 596 | "tokio-stream", 597 | "tracing", 598 | "tracing-opentelemetry", 599 | "tracing-subscriber", 600 | ] 601 | 602 | [[package]] 603 | name = "miniz_oxide" 604 | version = "0.7.1" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 607 | dependencies = [ 608 | "adler", 609 | ] 610 | 611 | [[package]] 612 | name = "mio" 613 | version = "0.8.11" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 616 | dependencies = [ 617 | "libc", 618 | "wasi", 619 | "windows-sys", 620 | ] 621 | 622 | [[package]] 623 | name = "nu-ansi-term" 624 | version = "0.46.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 627 | dependencies = [ 628 | "overload", 629 | "winapi", 630 | ] 631 | 632 | [[package]] 633 | name = "num-traits" 634 | version = "0.2.16" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" 637 | dependencies = [ 638 | "autocfg", 639 | ] 640 | 641 | [[package]] 642 | name = "num_cpus" 643 | version = "1.16.0" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 646 | dependencies = [ 647 | "hermit-abi", 648 | "libc", 649 | ] 650 | 651 | [[package]] 652 | name = "object" 653 | version = "0.32.1" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 656 | dependencies = [ 657 | "memchr", 658 | ] 659 | 660 | [[package]] 661 | name = "once_cell" 662 | version = "1.18.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 665 | 666 | [[package]] 667 | name = "opentelemetry" 668 | version = "0.20.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" 671 | dependencies = [ 672 | "opentelemetry_api", 673 | "opentelemetry_sdk", 674 | ] 675 | 676 | [[package]] 677 | name = "opentelemetry-aws" 678 | version = "0.8.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "31120a0109c172a42096766ef10e772f4a89422932be2c3b7f335858ff49380d" 681 | dependencies = [ 682 | "once_cell", 683 | "opentelemetry_api", 684 | ] 685 | 686 | [[package]] 687 | name = "opentelemetry-otlp" 688 | version = "0.13.0" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" 691 | dependencies = [ 692 | "async-trait", 693 | "futures-core", 694 | "http", 695 | "opentelemetry-proto", 696 | "opentelemetry-semantic-conventions", 697 | "opentelemetry_api", 698 | "opentelemetry_sdk", 699 | "prost", 700 | "thiserror", 701 | "tokio", 702 | "tonic", 703 | ] 704 | 705 | [[package]] 706 | name = "opentelemetry-proto" 707 | version = "0.3.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" 710 | dependencies = [ 711 | "opentelemetry_api", 712 | "opentelemetry_sdk", 713 | "prost", 714 | "tonic", 715 | ] 716 | 717 | [[package]] 718 | name = "opentelemetry-semantic-conventions" 719 | version = "0.12.0" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" 722 | dependencies = [ 723 | "opentelemetry", 724 | ] 725 | 726 | [[package]] 727 | name = "opentelemetry_api" 728 | version = "0.20.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "8a81f725323db1b1206ca3da8bb19874bbd3f57c3bcd59471bfb04525b265b9b" 731 | dependencies = [ 732 | "futures-channel", 733 | "futures-util", 734 | "indexmap", 735 | "js-sys", 736 | "once_cell", 737 | "pin-project-lite", 738 | "thiserror", 739 | "urlencoding", 740 | ] 741 | 742 | [[package]] 743 | name = "opentelemetry_sdk" 744 | version = "0.20.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" 747 | dependencies = [ 748 | "async-trait", 749 | "crossbeam-channel", 750 | "futures-channel", 751 | "futures-executor", 752 | "futures-util", 753 | "once_cell", 754 | "opentelemetry_api", 755 | "ordered-float", 756 | "percent-encoding", 757 | "rand", 758 | "regex", 759 | "serde_json", 760 | "thiserror", 761 | ] 762 | 763 | [[package]] 764 | name = "ordered-float" 765 | version = "3.9.1" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "2a54938017eacd63036332b4ae5c8a49fc8c0c1d6d629893057e4f13609edd06" 768 | dependencies = [ 769 | "num-traits", 770 | ] 771 | 772 | [[package]] 773 | name = "overload" 774 | version = "0.1.1" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 777 | 778 | [[package]] 779 | name = "parking_lot" 780 | version = "0.12.1" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 783 | dependencies = [ 784 | "lock_api", 785 | "parking_lot_core", 786 | ] 787 | 788 | [[package]] 789 | name = "parking_lot_core" 790 | version = "0.9.8" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 793 | dependencies = [ 794 | "cfg-if", 795 | "libc", 796 | "redox_syscall", 797 | "smallvec", 798 | "windows-targets", 799 | ] 800 | 801 | [[package]] 802 | name = "percent-encoding" 803 | version = "2.3.0" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 806 | 807 | [[package]] 808 | name = "pin-project" 809 | version = "1.1.3" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 812 | dependencies = [ 813 | "pin-project-internal", 814 | ] 815 | 816 | [[package]] 817 | name = "pin-project-internal" 818 | version = "1.1.3" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 821 | dependencies = [ 822 | "proc-macro2", 823 | "quote", 824 | "syn 2.0.32", 825 | ] 826 | 827 | [[package]] 828 | name = "pin-project-lite" 829 | version = "0.2.13" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 832 | 833 | [[package]] 834 | name = "pin-utils" 835 | version = "0.1.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 838 | 839 | [[package]] 840 | name = "ppv-lite86" 841 | version = "0.2.17" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 844 | 845 | [[package]] 846 | name = "proc-macro2" 847 | version = "1.0.66" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 850 | dependencies = [ 851 | "unicode-ident", 852 | ] 853 | 854 | [[package]] 855 | name = "prost" 856 | version = "0.11.9" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" 859 | dependencies = [ 860 | "bytes", 861 | "prost-derive", 862 | ] 863 | 864 | [[package]] 865 | name = "prost-derive" 866 | version = "0.11.9" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" 869 | dependencies = [ 870 | "anyhow", 871 | "itertools", 872 | "proc-macro2", 873 | "quote", 874 | "syn 1.0.109", 875 | ] 876 | 877 | [[package]] 878 | name = "quote" 879 | version = "1.0.33" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 882 | dependencies = [ 883 | "proc-macro2", 884 | ] 885 | 886 | [[package]] 887 | name = "rand" 888 | version = "0.8.5" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 891 | dependencies = [ 892 | "libc", 893 | "rand_chacha", 894 | "rand_core", 895 | ] 896 | 897 | [[package]] 898 | name = "rand_chacha" 899 | version = "0.3.1" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 902 | dependencies = [ 903 | "ppv-lite86", 904 | "rand_core", 905 | ] 906 | 907 | [[package]] 908 | name = "rand_core" 909 | version = "0.6.4" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 912 | dependencies = [ 913 | "getrandom", 914 | ] 915 | 916 | [[package]] 917 | name = "redox_syscall" 918 | version = "0.3.5" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 921 | dependencies = [ 922 | "bitflags", 923 | ] 924 | 925 | [[package]] 926 | name = "regex" 927 | version = "1.9.5" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" 930 | dependencies = [ 931 | "aho-corasick", 932 | "memchr", 933 | "regex-automata 0.3.8", 934 | "regex-syntax 0.7.5", 935 | ] 936 | 937 | [[package]] 938 | name = "regex-automata" 939 | version = "0.1.10" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 942 | dependencies = [ 943 | "regex-syntax 0.6.29", 944 | ] 945 | 946 | [[package]] 947 | name = "regex-automata" 948 | version = "0.3.8" 949 | source = "registry+https://github.com/rust-lang/crates.io-index" 950 | checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" 951 | dependencies = [ 952 | "aho-corasick", 953 | "memchr", 954 | "regex-syntax 0.7.5", 955 | ] 956 | 957 | [[package]] 958 | name = "regex-syntax" 959 | version = "0.6.29" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 962 | 963 | [[package]] 964 | name = "regex-syntax" 965 | version = "0.7.5" 966 | source = "registry+https://github.com/rust-lang/crates.io-index" 967 | checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" 968 | 969 | [[package]] 970 | name = "rustc-demangle" 971 | version = "0.1.23" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 974 | 975 | [[package]] 976 | name = "rustversion" 977 | version = "1.0.14" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 980 | 981 | [[package]] 982 | name = "ryu" 983 | version = "1.0.15" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 986 | 987 | [[package]] 988 | name = "scopeguard" 989 | version = "1.2.0" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 992 | 993 | [[package]] 994 | name = "serde" 995 | version = "1.0.188" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" 998 | dependencies = [ 999 | "serde_derive", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "serde_derive" 1004 | version = "1.0.188" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" 1007 | dependencies = [ 1008 | "proc-macro2", 1009 | "quote", 1010 | "syn 2.0.32", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "serde_json" 1015 | version = "1.0.106" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" 1018 | dependencies = [ 1019 | "itoa", 1020 | "ryu", 1021 | "serde", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "sharded-slab" 1026 | version = "0.1.4" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1029 | dependencies = [ 1030 | "lazy_static", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "signal-hook-registry" 1035 | version = "1.4.1" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 1038 | dependencies = [ 1039 | "libc", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "slab" 1044 | version = "0.4.9" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1047 | dependencies = [ 1048 | "autocfg", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "smallvec" 1053 | version = "1.11.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 1056 | 1057 | [[package]] 1058 | name = "socket2" 1059 | version = "0.4.9" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1062 | dependencies = [ 1063 | "libc", 1064 | "winapi", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "socket2" 1069 | version = "0.5.3" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" 1072 | dependencies = [ 1073 | "libc", 1074 | "windows-sys", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "strsim" 1079 | version = "0.10.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1082 | 1083 | [[package]] 1084 | name = "syn" 1085 | version = "1.0.109" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1088 | dependencies = [ 1089 | "proc-macro2", 1090 | "quote", 1091 | "unicode-ident", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "syn" 1096 | version = "2.0.32" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" 1099 | dependencies = [ 1100 | "proc-macro2", 1101 | "quote", 1102 | "unicode-ident", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "sync_wrapper" 1107 | version = "0.1.2" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1110 | 1111 | [[package]] 1112 | name = "thiserror" 1113 | version = "1.0.48" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" 1116 | dependencies = [ 1117 | "thiserror-impl", 1118 | ] 1119 | 1120 | [[package]] 1121 | name = "thiserror-impl" 1122 | version = "1.0.48" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" 1125 | dependencies = [ 1126 | "proc-macro2", 1127 | "quote", 1128 | "syn 2.0.32", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "thread_local" 1133 | version = "1.1.7" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 1136 | dependencies = [ 1137 | "cfg-if", 1138 | "once_cell", 1139 | ] 1140 | 1141 | [[package]] 1142 | name = "tokio" 1143 | version = "1.32.0" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 1146 | dependencies = [ 1147 | "backtrace", 1148 | "bytes", 1149 | "libc", 1150 | "mio", 1151 | "num_cpus", 1152 | "parking_lot", 1153 | "pin-project-lite", 1154 | "signal-hook-registry", 1155 | "socket2 0.5.3", 1156 | "tokio-macros", 1157 | "windows-sys", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "tokio-io-timeout" 1162 | version = "1.2.0" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" 1165 | dependencies = [ 1166 | "pin-project-lite", 1167 | "tokio", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "tokio-macros" 1172 | version = "2.1.0" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1175 | dependencies = [ 1176 | "proc-macro2", 1177 | "quote", 1178 | "syn 2.0.32", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "tokio-stream" 1183 | version = "0.1.14" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 1186 | dependencies = [ 1187 | "futures-core", 1188 | "pin-project-lite", 1189 | "tokio", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "tokio-util" 1194 | version = "0.7.8" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" 1197 | dependencies = [ 1198 | "bytes", 1199 | "futures-core", 1200 | "futures-sink", 1201 | "pin-project-lite", 1202 | "tokio", 1203 | "tracing", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "tonic" 1208 | version = "0.9.2" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" 1211 | dependencies = [ 1212 | "async-trait", 1213 | "axum", 1214 | "base64", 1215 | "bytes", 1216 | "futures-core", 1217 | "futures-util", 1218 | "h2", 1219 | "http", 1220 | "http-body", 1221 | "hyper", 1222 | "hyper-timeout", 1223 | "percent-encoding", 1224 | "pin-project", 1225 | "prost", 1226 | "tokio", 1227 | "tokio-stream", 1228 | "tower", 1229 | "tower-layer", 1230 | "tower-service", 1231 | "tracing", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "tower" 1236 | version = "0.4.13" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1239 | dependencies = [ 1240 | "futures-core", 1241 | "futures-util", 1242 | "indexmap", 1243 | "pin-project", 1244 | "pin-project-lite", 1245 | "rand", 1246 | "slab", 1247 | "tokio", 1248 | "tokio-util", 1249 | "tower-layer", 1250 | "tower-service", 1251 | "tracing", 1252 | ] 1253 | 1254 | [[package]] 1255 | name = "tower-layer" 1256 | version = "0.3.2" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1259 | 1260 | [[package]] 1261 | name = "tower-service" 1262 | version = "0.3.2" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1265 | 1266 | [[package]] 1267 | name = "tracing" 1268 | version = "0.1.37" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1271 | dependencies = [ 1272 | "cfg-if", 1273 | "pin-project-lite", 1274 | "tracing-attributes", 1275 | "tracing-core", 1276 | ] 1277 | 1278 | [[package]] 1279 | name = "tracing-attributes" 1280 | version = "0.1.26" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" 1283 | dependencies = [ 1284 | "proc-macro2", 1285 | "quote", 1286 | "syn 2.0.32", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "tracing-core" 1291 | version = "0.1.31" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 1294 | dependencies = [ 1295 | "once_cell", 1296 | "valuable", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "tracing-log" 1301 | version = "0.1.3" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1304 | dependencies = [ 1305 | "lazy_static", 1306 | "log", 1307 | "tracing-core", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "tracing-opentelemetry" 1312 | version = "0.21.0" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" 1315 | dependencies = [ 1316 | "once_cell", 1317 | "opentelemetry", 1318 | "opentelemetry_sdk", 1319 | "smallvec", 1320 | "tracing", 1321 | "tracing-core", 1322 | "tracing-log", 1323 | "tracing-subscriber", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "tracing-subscriber" 1328 | version = "0.3.17" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 1331 | dependencies = [ 1332 | "matchers", 1333 | "nu-ansi-term", 1334 | "once_cell", 1335 | "regex", 1336 | "sharded-slab", 1337 | "smallvec", 1338 | "thread_local", 1339 | "tracing", 1340 | "tracing-core", 1341 | "tracing-log", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "try-lock" 1346 | version = "0.2.4" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 1349 | 1350 | [[package]] 1351 | name = "unicode-ident" 1352 | version = "1.0.11" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 1355 | 1356 | [[package]] 1357 | name = "urlencoding" 1358 | version = "2.1.3" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 1361 | 1362 | [[package]] 1363 | name = "utf8parse" 1364 | version = "0.2.1" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1367 | 1368 | [[package]] 1369 | name = "valuable" 1370 | version = "0.1.0" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1373 | 1374 | [[package]] 1375 | name = "want" 1376 | version = "0.3.1" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1379 | dependencies = [ 1380 | "try-lock", 1381 | ] 1382 | 1383 | [[package]] 1384 | name = "wasi" 1385 | version = "0.11.0+wasi-snapshot-preview1" 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" 1387 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1388 | 1389 | [[package]] 1390 | name = "wasm-bindgen" 1391 | version = "0.2.87" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" 1394 | dependencies = [ 1395 | "cfg-if", 1396 | "wasm-bindgen-macro", 1397 | ] 1398 | 1399 | [[package]] 1400 | name = "wasm-bindgen-backend" 1401 | version = "0.2.87" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" 1404 | dependencies = [ 1405 | "bumpalo", 1406 | "log", 1407 | "once_cell", 1408 | "proc-macro2", 1409 | "quote", 1410 | "syn 2.0.32", 1411 | "wasm-bindgen-shared", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "wasm-bindgen-macro" 1416 | version = "0.2.87" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" 1419 | dependencies = [ 1420 | "quote", 1421 | "wasm-bindgen-macro-support", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "wasm-bindgen-macro-support" 1426 | version = "0.2.87" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" 1429 | dependencies = [ 1430 | "proc-macro2", 1431 | "quote", 1432 | "syn 2.0.32", 1433 | "wasm-bindgen-backend", 1434 | "wasm-bindgen-shared", 1435 | ] 1436 | 1437 | [[package]] 1438 | name = "wasm-bindgen-shared" 1439 | version = "0.2.87" 1440 | source = "registry+https://github.com/rust-lang/crates.io-index" 1441 | checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" 1442 | 1443 | [[package]] 1444 | name = "winapi" 1445 | version = "0.3.9" 1446 | source = "registry+https://github.com/rust-lang/crates.io-index" 1447 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1448 | dependencies = [ 1449 | "winapi-i686-pc-windows-gnu", 1450 | "winapi-x86_64-pc-windows-gnu", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "winapi-i686-pc-windows-gnu" 1455 | version = "0.4.0" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1458 | 1459 | [[package]] 1460 | name = "winapi-x86_64-pc-windows-gnu" 1461 | version = "0.4.0" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1464 | 1465 | [[package]] 1466 | name = "windows-sys" 1467 | version = "0.48.0" 1468 | source = "registry+https://github.com/rust-lang/crates.io-index" 1469 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1470 | dependencies = [ 1471 | "windows-targets", 1472 | ] 1473 | 1474 | [[package]] 1475 | name = "windows-targets" 1476 | version = "0.48.5" 1477 | source = "registry+https://github.com/rust-lang/crates.io-index" 1478 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1479 | dependencies = [ 1480 | "windows_aarch64_gnullvm", 1481 | "windows_aarch64_msvc", 1482 | "windows_i686_gnu", 1483 | "windows_i686_msvc", 1484 | "windows_x86_64_gnu", 1485 | "windows_x86_64_gnullvm", 1486 | "windows_x86_64_msvc", 1487 | ] 1488 | 1489 | [[package]] 1490 | name = "windows_aarch64_gnullvm" 1491 | version = "0.48.5" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1494 | 1495 | [[package]] 1496 | name = "windows_aarch64_msvc" 1497 | version = "0.48.5" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1500 | 1501 | [[package]] 1502 | name = "windows_i686_gnu" 1503 | version = "0.48.5" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1506 | 1507 | [[package]] 1508 | name = "windows_i686_msvc" 1509 | version = "0.48.5" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1512 | 1513 | [[package]] 1514 | name = "windows_x86_64_gnu" 1515 | version = "0.48.5" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1518 | 1519 | [[package]] 1520 | name = "windows_x86_64_gnullvm" 1521 | version = "0.48.5" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1524 | 1525 | [[package]] 1526 | name = "windows_x86_64_msvc" 1527 | version = "0.48.5" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1530 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Carl Lerche "] 3 | edition = "2018" 4 | name = "mini-redis" 5 | version = "0.4.1" 6 | license = "MIT" 7 | readme = "README.md" 8 | documentation = "https://docs.rs/mini-redis/0.4.0/mini-redis/" 9 | repository = "https://github.com/tokio-rs/mini-redis" 10 | description = """ 11 | An incomplete implementation of a Rust client and server. Used as a 12 | larger example of an idiomatic Tokio application. 13 | """ 14 | 15 | [[bin]] 16 | name = "mini-redis-cli" 17 | path = "src/bin/cli.rs" 18 | 19 | [[bin]] 20 | name = "mini-redis-server" 21 | path = "src/bin/server.rs" 22 | 23 | [dependencies] 24 | async-stream = "0.3.0" 25 | atoi = "2.0.0" 26 | bytes = "1" 27 | clap = { version = "4.2.7", features = ["derive"] } 28 | tokio = { version = "1", features = ["full"] } 29 | tokio-stream = "0.1" 30 | tracing = "0.1.34" 31 | tracing-subscriber = { version = "0.3.11", features = ["env-filter"] } 32 | # Implements the types defined in the OTel spec 33 | opentelemetry = { version = "0.20.0", optional = true } 34 | # Integration between the tracing crate and the opentelemetry crate 35 | tracing-opentelemetry = { version = "0.21.0", optional = true } 36 | # Provides a "propagator" to pass along an XrayId across services 37 | opentelemetry-aws = { version = "0.8.0", optional = true } 38 | # Allows you to send data to the OTel collector 39 | opentelemetry-otlp = { version = "0.13.0", optional = true } 40 | 41 | [dev-dependencies] 42 | # Enable test-utilities in dev mode only. This is mostly for tests. 43 | tokio = { version = "1", features = ["test-util"] } 44 | 45 | [features] 46 | otel = ["dep:opentelemetry", "dep:tracing-opentelemetry", "dep:opentelemetry-aws", "dep:opentelemetry-otlp"] 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Tokio Contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-redis 2 | 3 | `mini-redis` is an incomplete, idiomatic implementation of a 4 | [Redis](https://redis.io) client and server built with 5 | [Tokio](https://tokio.rs). 6 | 7 | The intent of this project is to provide a larger example of writing a Tokio 8 | application. 9 | 10 | **Disclaimer** Please don't use mini-redis in production. This project is 11 | intended to be a learning resource, and omits various parts of the Redis 12 | protocol because implementing them would not introduce any new concepts. We will 13 | not add new features because you need them in your project — use one of the 14 | fully featured alternatives instead. 15 | 16 | ## Why Redis 17 | 18 | The primary goal of this project is teaching Tokio. Doing this requires a 19 | project with a wide range of features with a focus on implementation simplicity. 20 | Redis, an in-memory database, provides a wide range of features and uses a 21 | simple wire protocol. The wide range of features allows demonstrating many Tokio 22 | patterns in a "real world" context. 23 | 24 | The Redis wire protocol documentation can be found [here](https://redis.io/topics/protocol). 25 | 26 | The set of commands Redis provides can be found 27 | [here](https://redis.io/commands). 28 | 29 | 30 | ## Running 31 | 32 | The repository provides a server, client library, and some client executables 33 | for interacting with the server. 34 | 35 | Start the server: 36 | 37 | ``` 38 | RUST_LOG=debug cargo run --bin mini-redis-server 39 | ``` 40 | 41 | The [`tracing`](https://github.com/tokio-rs/tracing) crate is used to provide structured logs. 42 | You can substitute `debug` with the desired [log level][level]. 43 | 44 | [level]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives 45 | 46 | Then, in a different terminal window, the various client [examples](examples) 47 | can be executed. For example: 48 | 49 | ``` 50 | cargo run --example hello_world 51 | ``` 52 | 53 | Additionally, a CLI client is provided to run arbitrary commands from the 54 | terminal. With the server running, the following works: 55 | 56 | ``` 57 | cargo run --bin mini-redis-cli set foo bar 58 | 59 | cargo run --bin mini-redis-cli get foo 60 | ``` 61 | 62 | ## OpenTelemetry 63 | 64 | If you are running many instances of your application (which is usually the case 65 | when you are developing a cloud service, for example), you need a way to get all 66 | of your trace data out of your host and into a centralized place. There are many 67 | options here, such as Prometheus, Jaeger, DataDog, Honeycomb, AWS X-Ray etc. 68 | 69 | We leverage OpenTelemetry, because it's an open standard that allows for a 70 | single data format to be used for all the options mentioned above (and more). 71 | This eliminates the risk of vendor lock-in, since you can switch between 72 | providers if needed. 73 | 74 | ### AWS X-Ray example 75 | 76 | To enable sending traces to X-Ray, use the `otel` feature: 77 | ``` 78 | RUST_LOG=debug cargo run --bin mini-redis-server --features otel 79 | ``` 80 | 81 | This will switch `tracing` to use `tracing-opentelemetry`. You will need to 82 | have a copy of AWSOtelCollector running on the same host. 83 | 84 | For demo purposes, you can follow the setup documented at 85 | https://github.com/aws-observability/aws-otel-collector/blob/main/docs/developers/docker-demo.md#run-a-single-aws-otel-collector-instance-in-docker 86 | 87 | ## Supported commands 88 | 89 | `mini-redis` currently supports the following commands. 90 | 91 | * [PING](https://redis.io/commands/ping) 92 | * [GET](https://redis.io/commands/get) 93 | * [SET](https://redis.io/commands/set) 94 | * [PUBLISH](https://redis.io/commands/publish) 95 | * [SUBSCRIBE](https://redis.io/commands/subscribe) 96 | 97 | The Redis wire protocol specification can be found 98 | [here](https://redis.io/topics/protocol). 99 | 100 | There is no support for persistence yet. 101 | 102 | ## Tokio patterns 103 | 104 | The project demonstrates a number of useful patterns, including: 105 | 106 | ### TCP server 107 | 108 | [`server.rs`](src/server.rs) starts a TCP server that accepts connections, 109 | and spawns a new task per connection. It gracefully handles `accept` errors. 110 | 111 | ### Client library 112 | 113 | [`client.rs`](src/clients/client.rs) shows how to model an asynchronous client. The 114 | various capabilities are exposed as `async` methods. 115 | 116 | ### State shared across sockets 117 | 118 | The server maintains a [`Db`] instance that is accessible from all connected 119 | connections. The [`Db`] instance manages the key-value state as well as pub/sub 120 | capabilities. 121 | 122 | [`Db`]: src/db.rs 123 | 124 | ### Framing 125 | 126 | [`connection.rs`](src/connection.rs) and [`frame.rs`](src/frame.rs) show how to 127 | idiomatically implement a wire protocol. The protocol is modeled using an 128 | intermediate representation, the `Frame` structure. `Connection` takes a 129 | `TcpStream` and exposes an API that sends and receives `Frame` values. 130 | 131 | ### Graceful shutdown 132 | 133 | The server implements graceful shutdown. [`tokio::signal`] is used to listen for 134 | a SIGINT. Once the signal is received, shutdown begins. The server stops 135 | accepting new connections. Existing connections are notified to shutdown 136 | gracefully. In-flight work is completed, and the connection is closed. 137 | 138 | [`tokio::signal`]: https://docs.rs/tokio/*/tokio/signal/ 139 | 140 | ### Concurrent connection limiting 141 | 142 | The server uses a [`Semaphore`] limits the maximum number of concurrent 143 | connections. Once the limit is reached, the server stops accepting new 144 | connections until an existing one terminates. 145 | 146 | [`Semaphore`]: https://docs.rs/tokio/*/tokio/sync/struct.Semaphore.html 147 | 148 | ### Pub/Sub 149 | 150 | The server implements non-trivial pub/sub capability. The client may subscribe 151 | to multiple channels and update its subscription at any time. The server 152 | implements this using one [broadcast channel][broadcast] per channel and a 153 | [`StreamMap`] per connection. Clients are able to send subscription commands to 154 | the server to update the active subscriptions. 155 | 156 | [broadcast]: https://docs.rs/tokio/*/tokio/sync/broadcast/index.html 157 | [`StreamMap`]: https://docs.rs/tokio-stream/*/tokio_stream/struct.StreamMap.html 158 | 159 | ### Using a `std::sync::Mutex` in an async application 160 | 161 | The server uses a `std::sync::Mutex` and **not** a Tokio mutex to synchronize 162 | access to shared state. See [`db.rs`](src/db.rs) for more details. 163 | 164 | ### Testing asynchronous code that relies on time 165 | 166 | In [`tests/server.rs`](tests/server.rs), there are tests for key expiration. 167 | These tests depend on time passing. In order to make the tests deterministic, 168 | time is mocked out using Tokio's testing utilities. 169 | 170 | ## Contributing 171 | 172 | Contributions to `mini-redis` are welcome. Keep in mind, the goal of the project 173 | is **not** to reach feature parity with real Redis, but to demonstrate 174 | asynchronous Rust patterns with Tokio. 175 | 176 | Commands or other features should only be added if doing so is useful to 177 | demonstrate a new pattern. 178 | 179 | Contributions should come with extensive comments targeted to new Tokio users. 180 | 181 | Contributions that only focus on clarifying and improving comments are very 182 | welcome. 183 | 184 | ## License 185 | 186 | This project is licensed under the [MIT license](LICENSE). 187 | 188 | ### Contribution 189 | 190 | Unless you explicitly state otherwise, any contribution intentionally submitted 191 | for inclusion in `mini-redis` by you, shall be licensed as MIT, without any 192 | additional terms or conditions. 193 | -------------------------------------------------------------------------------- /examples/chat.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main] 2 | async fn main() { 3 | unimplemented!(); 4 | } 5 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | //! Hello world server. 2 | //! 3 | //! A simple client that connects to a mini-redis server, sets key "hello" with value "world", 4 | //! and gets it from the server after 5 | //! 6 | //! You can test this out by running: 7 | //! 8 | //! cargo run --bin mini-redis-server 9 | //! 10 | //! And then in another terminal run: 11 | //! 12 | //! cargo run --example hello_world 13 | 14 | #![warn(rust_2018_idioms)] 15 | 16 | use mini_redis::{clients::Client, Result}; 17 | 18 | #[tokio::main] 19 | pub async fn main() -> Result<()> { 20 | // Open a connection to the mini-redis address. 21 | let mut client = Client::connect("127.0.0.1:6379").await?; 22 | 23 | // Set the key "hello" with value "world" 24 | client.set("hello", "world".into()).await?; 25 | 26 | // Get key "hello" 27 | let result = client.get("hello").await?; 28 | 29 | println!("got value from the server; success={:?}", result.is_some()); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/pub.rs: -------------------------------------------------------------------------------- 1 | //! Publish to a redis channel example. 2 | //! 3 | //! A simple client that connects to a mini-redis server, and 4 | //! publishes a message on `foo` channel 5 | //! 6 | //! You can test this out by running: 7 | //! 8 | //! cargo run --bin mini-redis-server 9 | //! 10 | //! Then in another terminal run: 11 | //! 12 | //! cargo run --example sub 13 | //! 14 | //! And then in another terminal run: 15 | //! 16 | //! cargo run --example pub 17 | 18 | #![warn(rust_2018_idioms)] 19 | 20 | use mini_redis::{clients::Client, Result}; 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<()> { 24 | // Open a connection to the mini-redis address. 25 | let mut client = Client::connect("127.0.0.1:6379").await?; 26 | 27 | // publish message `bar` on channel foo 28 | client.publish("foo", "bar".into()).await?; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/sub.rs: -------------------------------------------------------------------------------- 1 | //! Subscribe to a redis channel example. 2 | //! 3 | //! A simple client that connects to a mini-redis server, subscribes to "foo" and "bar" channels 4 | //! and awaits messages published on those channels 5 | //! 6 | //! You can test this out by running: 7 | //! 8 | //! cargo run --bin mini-redis-server 9 | //! 10 | //! Then in another terminal run: 11 | //! 12 | //! cargo run --example sub 13 | //! 14 | //! And then in another terminal run: 15 | //! 16 | //! cargo run --example pub 17 | 18 | #![warn(rust_2018_idioms)] 19 | 20 | use mini_redis::{clients::Client, Result}; 21 | 22 | #[tokio::main] 23 | pub async fn main() -> Result<()> { 24 | // Open a connection to the mini-redis address. 25 | let client = Client::connect("127.0.0.1:6379").await?; 26 | 27 | // subscribe to channel foo 28 | let mut subscriber = client.subscribe(vec!["foo".into()]).await?; 29 | 30 | // await messages on channel foo 31 | if let Some(msg) = subscriber.next_message().await? { 32 | println!( 33 | "got message from the channel: {}; message = {:?}", 34 | msg.channel, msg.content 35 | ); 36 | } 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /src/bin/cli.rs: -------------------------------------------------------------------------------- 1 | use mini_redis::{clients::Client, DEFAULT_PORT}; 2 | 3 | use bytes::Bytes; 4 | use clap::{Parser, Subcommand}; 5 | use std::num::ParseIntError; 6 | use std::str; 7 | use std::time::Duration; 8 | 9 | #[derive(Parser, Debug)] 10 | #[command( 11 | name = "mini-redis-cli", 12 | version, 13 | author, 14 | about = "Issue Redis commands" 15 | )] 16 | struct Cli { 17 | #[clap(subcommand)] 18 | command: Command, 19 | 20 | #[arg(id = "hostname", long, default_value = "127.0.0.1")] 21 | host: String, 22 | 23 | #[arg(long, default_value_t = DEFAULT_PORT)] 24 | port: u16, 25 | } 26 | 27 | #[derive(Subcommand, Debug)] 28 | enum Command { 29 | Ping { 30 | /// Message to ping 31 | msg: Option, 32 | }, 33 | /// Get the value of key. 34 | Get { 35 | /// Name of key to get 36 | key: String, 37 | }, 38 | /// Set key to hold the string value. 39 | Set { 40 | /// Name of key to set 41 | key: String, 42 | 43 | /// Value to set. 44 | value: Bytes, 45 | 46 | /// Expire the value after specified amount of time 47 | #[arg(value_parser = duration_from_ms_str)] 48 | expires: Option, 49 | }, 50 | /// Publisher to send a message to a specific channel. 51 | Publish { 52 | /// Name of channel 53 | channel: String, 54 | 55 | /// Message to publish 56 | message: Bytes, 57 | }, 58 | /// Subscribe a client to a specific channel or channels. 59 | Subscribe { 60 | /// Specific channel or channels 61 | channels: Vec, 62 | }, 63 | } 64 | 65 | /// Entry point for CLI tool. 66 | /// 67 | /// The `[tokio::main]` annotation signals that the Tokio runtime should be 68 | /// started when the function is called. The body of the function is executed 69 | /// within the newly spawned runtime. 70 | /// 71 | /// `flavor = "current_thread"` is used here to avoid spawning background 72 | /// threads. The CLI tool use case benefits more by being lighter instead of 73 | /// multi-threaded. 74 | #[tokio::main(flavor = "current_thread")] 75 | async fn main() -> mini_redis::Result<()> { 76 | // Enable logging 77 | tracing_subscriber::fmt::try_init()?; 78 | 79 | // Parse command line arguments 80 | let cli = Cli::parse(); 81 | 82 | // Get the remote address to connect to 83 | let addr = format!("{}:{}", cli.host, cli.port); 84 | 85 | // Establish a connection 86 | let mut client = Client::connect(&addr).await?; 87 | 88 | // Process the requested command 89 | match cli.command { 90 | Command::Ping { msg } => { 91 | let value = client.ping(msg).await?; 92 | if let Ok(string) = str::from_utf8(&value) { 93 | println!("\"{}\"", string); 94 | } else { 95 | println!("{:?}", value); 96 | } 97 | } 98 | Command::Get { key } => { 99 | if let Some(value) = client.get(&key).await? { 100 | if let Ok(string) = str::from_utf8(&value) { 101 | println!("\"{}\"", string); 102 | } else { 103 | println!("{:?}", value); 104 | } 105 | } else { 106 | println!("(nil)"); 107 | } 108 | } 109 | Command::Set { 110 | key, 111 | value, 112 | expires: None, 113 | } => { 114 | client.set(&key, value).await?; 115 | println!("OK"); 116 | } 117 | Command::Set { 118 | key, 119 | value, 120 | expires: Some(expires), 121 | } => { 122 | client.set_expires(&key, value, expires).await?; 123 | println!("OK"); 124 | } 125 | Command::Publish { channel, message } => { 126 | client.publish(&channel, message).await?; 127 | println!("Publish OK"); 128 | } 129 | Command::Subscribe { channels } => { 130 | if channels.is_empty() { 131 | return Err("channel(s) must be provided".into()); 132 | } 133 | let mut subscriber = client.subscribe(channels).await?; 134 | 135 | // await messages on channels 136 | while let Some(msg) = subscriber.next_message().await? { 137 | println!( 138 | "got message from the channel: {}; message = {:?}", 139 | msg.channel, msg.content 140 | ); 141 | } 142 | } 143 | } 144 | 145 | Ok(()) 146 | } 147 | 148 | fn duration_from_ms_str(src: &str) -> Result { 149 | let ms = src.parse::()?; 150 | Ok(Duration::from_millis(ms)) 151 | } 152 | -------------------------------------------------------------------------------- /src/bin/server.rs: -------------------------------------------------------------------------------- 1 | //! mini-redis server. 2 | //! 3 | //! This file is the entry point for the server implemented in the library. It 4 | //! performs command line parsing and passes the arguments on to 5 | //! `mini_redis::server`. 6 | //! 7 | //! The `clap` crate is used for parsing arguments. 8 | 9 | use mini_redis::{server, DEFAULT_PORT}; 10 | 11 | use clap::Parser; 12 | use tokio::net::TcpListener; 13 | use tokio::signal; 14 | 15 | #[cfg(feature = "otel")] 16 | // To be able to set the XrayPropagator 17 | use opentelemetry::global; 18 | #[cfg(feature = "otel")] 19 | // To configure certain options such as sampling rate 20 | use opentelemetry::sdk::trace as sdktrace; 21 | #[cfg(feature = "otel")] 22 | // For passing along the same XrayId across services 23 | use opentelemetry_aws::trace::XrayPropagator; 24 | #[cfg(feature = "otel")] 25 | // The `Ext` traits are to allow the Registry to accept the 26 | // OpenTelemetry-specific types (such as `OpenTelemetryLayer`) 27 | use tracing_subscriber::{ 28 | fmt, layer::SubscriberExt, util::SubscriberInitExt, util::TryInitError, EnvFilter, 29 | }; 30 | 31 | #[tokio::main] 32 | pub async fn main() -> mini_redis::Result<()> { 33 | set_up_logging()?; 34 | 35 | let cli = Cli::parse(); 36 | let port = cli.port.unwrap_or(DEFAULT_PORT); 37 | 38 | // Bind a TCP listener 39 | let listener = TcpListener::bind(&format!("127.0.0.1:{}", port)).await?; 40 | 41 | server::run(listener, signal::ctrl_c()).await; 42 | 43 | Ok(()) 44 | } 45 | 46 | #[derive(Parser, Debug)] 47 | #[command(name = "mini-redis-server", version, author, about = "A Redis server")] 48 | struct Cli { 49 | #[arg(long)] 50 | port: Option, 51 | } 52 | 53 | #[cfg(not(feature = "otel"))] 54 | fn set_up_logging() -> mini_redis::Result<()> { 55 | // See https://docs.rs/tracing for more info 56 | tracing_subscriber::fmt::try_init() 57 | } 58 | 59 | #[cfg(feature = "otel")] 60 | fn set_up_logging() -> Result<(), TryInitError> { 61 | // Set the global propagator to X-Ray propagator 62 | // Note: If you need to pass the x-amzn-trace-id across services in the same trace, 63 | // you will need this line. However, this requires additional code not pictured here. 64 | // For a full example using hyper, see: 65 | // https://github.com/open-telemetry/opentelemetry-rust/blob/v0.19.0/examples/aws-xray/src/server.rs#L14-L26 66 | global::set_text_map_propagator(XrayPropagator::default()); 67 | 68 | let tracer = opentelemetry_otlp::new_pipeline() 69 | .tracing() 70 | .with_exporter(opentelemetry_otlp::new_exporter().tonic()) 71 | .with_trace_config( 72 | sdktrace::config() 73 | .with_sampler(sdktrace::Sampler::AlwaysOn) 74 | // Needed in order to convert the trace IDs into an Xray-compatible format 75 | .with_id_generator(sdktrace::XrayIdGenerator::default()), 76 | ) 77 | .install_simple() 78 | .expect("Unable to initialize OtlpPipeline"); 79 | 80 | // Create a tracing layer with the configured tracer 81 | let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); 82 | 83 | // Parse an `EnvFilter` configuration from the `RUST_LOG` 84 | // environment variable. 85 | let filter = EnvFilter::from_default_env(); 86 | 87 | // Use the tracing subscriber `Registry`, or any other subscriber 88 | // that impls `LookupSpan` 89 | tracing_subscriber::registry() 90 | .with(opentelemetry) 91 | .with(filter) 92 | .with(fmt::Layer::default()) 93 | .try_init() 94 | } 95 | -------------------------------------------------------------------------------- /src/clients/blocking_client.rs: -------------------------------------------------------------------------------- 1 | //! Minimal blocking Redis client implementation 2 | //! 3 | //! Provides a blocking connect and methods for issuing the supported commands. 4 | 5 | use bytes::Bytes; 6 | use std::time::Duration; 7 | use tokio::net::ToSocketAddrs; 8 | use tokio::runtime::Runtime; 9 | 10 | pub use crate::clients::Message; 11 | 12 | /// Established connection with a Redis server. 13 | /// 14 | /// Backed by a single `TcpStream`, `BlockingClient` provides basic network 15 | /// client functionality (no pooling, retrying, ...). Connections are 16 | /// established using the [`connect`](fn@connect) function. 17 | /// 18 | /// Requests are issued using the various methods of `Client`. 19 | pub struct BlockingClient { 20 | /// The asynchronous `Client`. 21 | inner: crate::clients::Client, 22 | 23 | /// A `current_thread` runtime for executing operations on the asynchronous 24 | /// client in a blocking manner. 25 | rt: Runtime, 26 | } 27 | 28 | /// A client that has entered pub/sub mode. 29 | /// 30 | /// Once clients subscribe to a channel, they may only perform pub/sub related 31 | /// commands. The `BlockingClient` type is transitioned to a 32 | /// `BlockingSubscriber` type in order to prevent non-pub/sub methods from being 33 | /// called. 34 | pub struct BlockingSubscriber { 35 | /// The asynchronous `Subscriber`. 36 | inner: crate::clients::Subscriber, 37 | 38 | /// A `current_thread` runtime for executing operations on the asynchronous 39 | /// `Subscriber` in a blocking manner. 40 | rt: Runtime, 41 | } 42 | 43 | /// The iterator returned by `Subscriber::into_iter`. 44 | struct SubscriberIterator { 45 | /// The asynchronous `Subscriber`. 46 | inner: crate::clients::Subscriber, 47 | 48 | /// A `current_thread` runtime for executing operations on the asynchronous 49 | /// `Subscriber` in a blocking manner. 50 | rt: Runtime, 51 | } 52 | 53 | impl BlockingClient { 54 | /// Establish a connection with the Redis server located at `addr`. 55 | /// 56 | /// `addr` may be any type that can be asynchronously converted to a 57 | /// `SocketAddr`. This includes `SocketAddr` and strings. The `ToSocketAddrs` 58 | /// trait is the Tokio version and not the `std` version. 59 | /// 60 | /// # Examples 61 | /// 62 | /// ```no_run 63 | /// use mini_redis::clients::BlockingClient; 64 | /// 65 | /// fn main() { 66 | /// let client = match BlockingClient::connect("localhost:6379") { 67 | /// Ok(client) => client, 68 | /// Err(_) => panic!("failed to establish connection"), 69 | /// }; 70 | /// # drop(client); 71 | /// } 72 | /// ``` 73 | pub fn connect(addr: T) -> crate::Result { 74 | let rt = tokio::runtime::Builder::new_current_thread() 75 | .enable_all() 76 | .build()?; 77 | 78 | let inner = rt.block_on(crate::clients::Client::connect(addr))?; 79 | 80 | Ok(BlockingClient { inner, rt }) 81 | } 82 | 83 | /// Get the value of key. 84 | /// 85 | /// If the key does not exist the special value `None` is returned. 86 | /// 87 | /// # Examples 88 | /// 89 | /// Demonstrates basic usage. 90 | /// 91 | /// ```no_run 92 | /// use mini_redis::clients::BlockingClient; 93 | /// 94 | /// fn main() { 95 | /// let mut client = BlockingClient::connect("localhost:6379").unwrap(); 96 | /// 97 | /// let val = client.get("foo").unwrap(); 98 | /// println!("Got = {:?}", val); 99 | /// } 100 | /// ``` 101 | pub fn get(&mut self, key: &str) -> crate::Result> { 102 | self.rt.block_on(self.inner.get(key)) 103 | } 104 | 105 | /// Set `key` to hold the given `value`. 106 | /// 107 | /// The `value` is associated with `key` until it is overwritten by the next 108 | /// call to `set` or it is removed. 109 | /// 110 | /// If key already holds a value, it is overwritten. Any previous time to 111 | /// live associated with the key is discarded on successful SET operation. 112 | /// 113 | /// # Examples 114 | /// 115 | /// Demonstrates basic usage. 116 | /// 117 | /// ```no_run 118 | /// use mini_redis::clients::BlockingClient; 119 | /// 120 | /// fn main() { 121 | /// let mut client = BlockingClient::connect("localhost:6379").unwrap(); 122 | /// 123 | /// client.set("foo", "bar".into()).unwrap(); 124 | /// 125 | /// // Getting the value immediately works 126 | /// let val = client.get("foo").unwrap().unwrap(); 127 | /// assert_eq!(val, "bar"); 128 | /// } 129 | /// ``` 130 | pub fn set(&mut self, key: &str, value: Bytes) -> crate::Result<()> { 131 | self.rt.block_on(self.inner.set(key, value)) 132 | } 133 | 134 | /// Set `key` to hold the given `value`. The value expires after `expiration` 135 | /// 136 | /// The `value` is associated with `key` until one of the following: 137 | /// - it expires. 138 | /// - it is overwritten by the next call to `set`. 139 | /// - it is removed. 140 | /// 141 | /// If key already holds a value, it is overwritten. Any previous time to 142 | /// live associated with the key is discarded on a successful SET operation. 143 | /// 144 | /// # Examples 145 | /// 146 | /// Demonstrates basic usage. This example is not **guaranteed** to always 147 | /// work as it relies on time based logic and assumes the client and server 148 | /// stay relatively synchronized in time. The real world tends to not be so 149 | /// favorable. 150 | /// 151 | /// ```no_run 152 | /// use mini_redis::clients::BlockingClient; 153 | /// use std::thread; 154 | /// use std::time::Duration; 155 | /// 156 | /// fn main() { 157 | /// let ttl = Duration::from_millis(500); 158 | /// let mut client = BlockingClient::connect("localhost:6379").unwrap(); 159 | /// 160 | /// client.set_expires("foo", "bar".into(), ttl).unwrap(); 161 | /// 162 | /// // Getting the value immediately works 163 | /// let val = client.get("foo").unwrap().unwrap(); 164 | /// assert_eq!(val, "bar"); 165 | /// 166 | /// // Wait for the TTL to expire 167 | /// thread::sleep(ttl); 168 | /// 169 | /// let val = client.get("foo").unwrap(); 170 | /// assert!(val.is_some()); 171 | /// } 172 | /// ``` 173 | pub fn set_expires( 174 | &mut self, 175 | key: &str, 176 | value: Bytes, 177 | expiration: Duration, 178 | ) -> crate::Result<()> { 179 | self.rt 180 | .block_on(self.inner.set_expires(key, value, expiration)) 181 | } 182 | 183 | /// Posts `message` to the given `channel`. 184 | /// 185 | /// Returns the number of subscribers currently listening on the channel. 186 | /// There is no guarantee that these subscribers receive the message as they 187 | /// may disconnect at any time. 188 | /// 189 | /// # Examples 190 | /// 191 | /// Demonstrates basic usage. 192 | /// 193 | /// ```no_run 194 | /// use mini_redis::clients::BlockingClient; 195 | /// 196 | /// fn main() { 197 | /// let mut client = BlockingClient::connect("localhost:6379").unwrap(); 198 | /// 199 | /// let val = client.publish("foo", "bar".into()).unwrap(); 200 | /// println!("Got = {:?}", val); 201 | /// } 202 | /// ``` 203 | pub fn publish(&mut self, channel: &str, message: Bytes) -> crate::Result { 204 | self.rt.block_on(self.inner.publish(channel, message)) 205 | } 206 | 207 | /// Subscribes the client to the specified channels. 208 | /// 209 | /// Once a client issues a subscribe command, it may no longer issue any 210 | /// non-pub/sub commands. The function consumes `self` and returns a 211 | /// `BlockingSubscriber`. 212 | /// 213 | /// The `BlockingSubscriber` value is used to receive messages as well as 214 | /// manage the list of channels the client is subscribed to. 215 | pub fn subscribe(self, channels: Vec) -> crate::Result { 216 | let subscriber = self.rt.block_on(self.inner.subscribe(channels))?; 217 | Ok(BlockingSubscriber { 218 | inner: subscriber, 219 | rt: self.rt, 220 | }) 221 | } 222 | } 223 | 224 | impl BlockingSubscriber { 225 | /// Returns the set of channels currently subscribed to. 226 | pub fn get_subscribed(&self) -> &[String] { 227 | self.inner.get_subscribed() 228 | } 229 | 230 | /// Receive the next message published on a subscribed channel, waiting if 231 | /// necessary. 232 | /// 233 | /// `None` indicates the subscription has been terminated. 234 | pub fn next_message(&mut self) -> crate::Result> { 235 | self.rt.block_on(self.inner.next_message()) 236 | } 237 | 238 | /// Convert the subscriber into an `Iterator` yielding new messages published 239 | /// on subscribed channels. 240 | pub fn into_iter(self) -> impl Iterator> { 241 | SubscriberIterator { 242 | inner: self.inner, 243 | rt: self.rt, 244 | } 245 | } 246 | 247 | /// Subscribe to a list of new channels 248 | pub fn subscribe(&mut self, channels: &[String]) -> crate::Result<()> { 249 | self.rt.block_on(self.inner.subscribe(channels)) 250 | } 251 | 252 | /// Unsubscribe to a list of new channels 253 | pub fn unsubscribe(&mut self, channels: &[String]) -> crate::Result<()> { 254 | self.rt.block_on(self.inner.unsubscribe(channels)) 255 | } 256 | } 257 | 258 | impl Iterator for SubscriberIterator { 259 | type Item = crate::Result; 260 | 261 | fn next(&mut self) -> Option> { 262 | self.rt.block_on(self.inner.next_message()).transpose() 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/clients/buffered_client.rs: -------------------------------------------------------------------------------- 1 | use crate::clients::Client; 2 | use crate::Result; 3 | 4 | use bytes::Bytes; 5 | use tokio::sync::mpsc::{channel, Receiver, Sender}; 6 | use tokio::sync::oneshot; 7 | 8 | // Enum used to message pass the requested command from the `BufferedClient` handle 9 | #[derive(Debug)] 10 | enum Command { 11 | Get(String), 12 | Set(String, Bytes), 13 | } 14 | 15 | // Message type sent over the channel to the connection task. 16 | // 17 | // `Command` is the command to forward to the connection. 18 | // 19 | // `oneshot::Sender` is a channel type that sends a **single** value. It is used 20 | // here to send the response received from the connection back to the original 21 | // requester. 22 | type Message = (Command, oneshot::Sender>>); 23 | 24 | /// Receive commands sent through the channel and forward them to client. The 25 | /// response is returned back to the caller via a `oneshot`. 26 | async fn run(mut client: Client, mut rx: Receiver) { 27 | // Repeatedly pop messages from the channel. A return value of `None` 28 | // indicates that all `BufferedClient` handles have dropped and there will never be 29 | // another message sent on the channel. 30 | while let Some((cmd, tx)) = rx.recv().await { 31 | // The command is forwarded to the connection 32 | let response = match cmd { 33 | Command::Get(key) => client.get(&key).await, 34 | Command::Set(key, value) => client.set(&key, value).await.map(|_| None), 35 | }; 36 | 37 | // Send the response back to the caller. 38 | // 39 | // Failing to send the message indicates the `rx` half dropped 40 | // before receiving the message. This is a normal runtime event. 41 | let _ = tx.send(response); 42 | } 43 | } 44 | 45 | #[derive(Clone)] 46 | pub struct BufferedClient { 47 | tx: Sender, 48 | } 49 | 50 | impl BufferedClient { 51 | /// Create a new client request buffer 52 | /// 53 | /// The `Client` performs Redis commands directly on the TCP connection. Only a 54 | /// single request may be in-flight at a given time and operations require 55 | /// mutable access to the `Client` handle. This prevents using a single Redis 56 | /// connection from multiple Tokio tasks. 57 | /// 58 | /// The strategy for dealing with this class of problem is to spawn a dedicated 59 | /// Tokio task to manage the Redis connection and using "message passing" to 60 | /// operate on the connection. Commands are pushed into a channel. The 61 | /// connection task pops commands off of the channel and applies them to the 62 | /// Redis connection. When the response is received, it is forwarded to the 63 | /// original requester. 64 | /// 65 | /// The returned `BufferedClient` handle may be cloned before passing the new handle to 66 | /// separate tasks. 67 | pub fn buffer(client: Client) -> BufferedClient { 68 | // Setting the message limit to a hard coded value of 32. in a real-app, the 69 | // buffer size should be configurable, but we don't need to do that here. 70 | let (tx, rx) = channel(32); 71 | 72 | // Spawn a task to process requests for the connection. 73 | tokio::spawn(async move { run(client, rx).await }); 74 | 75 | // Return the `BufferedClient` handle. 76 | BufferedClient { tx } 77 | } 78 | 79 | /// Get the value of a key. 80 | /// 81 | /// Same as `Client::get` but requests are **buffered** until the associated 82 | /// connection has the ability to send the request. 83 | pub async fn get(&mut self, key: &str) -> Result> { 84 | // Initialize a new `Get` command to send via the channel. 85 | let get = Command::Get(key.into()); 86 | 87 | // Initialize a new oneshot to be used to receive the response back from the connection. 88 | let (tx, rx) = oneshot::channel(); 89 | 90 | // Send the request 91 | self.tx.send((get, tx)).await?; 92 | 93 | // Await the response 94 | match rx.await { 95 | Ok(res) => res, 96 | Err(err) => Err(err.into()), 97 | } 98 | } 99 | 100 | /// Set `key` to hold the given `value`. 101 | /// 102 | /// Same as `Client::set` but requests are **buffered** until the associated 103 | /// connection has the ability to send the request 104 | pub async fn set(&mut self, key: &str, value: Bytes) -> Result<()> { 105 | // Initialize a new `Set` command to send via the channel. 106 | let set = Command::Set(key.into(), value); 107 | 108 | // Initialize a new oneshot to be used to receive the response back from the connection. 109 | let (tx, rx) = oneshot::channel(); 110 | 111 | // Send the request 112 | self.tx.send((set, tx)).await?; 113 | 114 | // Await the response 115 | match rx.await { 116 | Ok(res) => res.map(|_| ()), 117 | Err(err) => Err(err.into()), 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/clients/client.rs: -------------------------------------------------------------------------------- 1 | //! Minimal Redis client implementation 2 | //! 3 | //! Provides an async connect and methods for issuing the supported commands. 4 | 5 | use crate::cmd::{Get, Ping, Publish, Set, Subscribe, Unsubscribe}; 6 | use crate::{Connection, Frame}; 7 | 8 | use async_stream::try_stream; 9 | use bytes::Bytes; 10 | use std::io::{Error, ErrorKind}; 11 | use std::time::Duration; 12 | use tokio::net::{TcpStream, ToSocketAddrs}; 13 | use tokio_stream::Stream; 14 | use tracing::{debug, instrument}; 15 | 16 | /// Established connection with a Redis server. 17 | /// 18 | /// Backed by a single `TcpStream`, `Client` provides basic network client 19 | /// functionality (no pooling, retrying, ...). Connections are established using 20 | /// the [`connect`](fn@connect) function. 21 | /// 22 | /// Requests are issued using the various methods of `Client`. 23 | pub struct Client { 24 | /// The TCP connection decorated with the redis protocol encoder / decoder 25 | /// implemented using a buffered `TcpStream`. 26 | /// 27 | /// When `Listener` receives an inbound connection, the `TcpStream` is 28 | /// passed to `Connection::new`, which initializes the associated buffers. 29 | /// `Connection` allows the handler to operate at the "frame" level and keep 30 | /// the byte level protocol parsing details encapsulated in `Connection`. 31 | connection: Connection, 32 | } 33 | 34 | /// A client that has entered pub/sub mode. 35 | /// 36 | /// Once clients subscribe to a channel, they may only perform pub/sub related 37 | /// commands. The `Client` type is transitioned to a `Subscriber` type in order 38 | /// to prevent non-pub/sub methods from being called. 39 | pub struct Subscriber { 40 | /// The subscribed client. 41 | client: Client, 42 | 43 | /// The set of channels to which the `Subscriber` is currently subscribed. 44 | subscribed_channels: Vec, 45 | } 46 | 47 | /// A message received on a subscribed channel. 48 | #[derive(Debug, Clone)] 49 | pub struct Message { 50 | pub channel: String, 51 | pub content: Bytes, 52 | } 53 | 54 | impl Client { 55 | /// Establish a connection with the Redis server located at `addr`. 56 | /// 57 | /// `addr` may be any type that can be asynchronously converted to a 58 | /// `SocketAddr`. This includes `SocketAddr` and strings. The `ToSocketAddrs` 59 | /// trait is the Tokio version and not the `std` version. 60 | /// 61 | /// # Examples 62 | /// 63 | /// ```no_run 64 | /// use mini_redis::clients::Client; 65 | /// 66 | /// #[tokio::main] 67 | /// async fn main() { 68 | /// let client = match Client::connect("localhost:6379").await { 69 | /// Ok(client) => client, 70 | /// Err(_) => panic!("failed to establish connection"), 71 | /// }; 72 | /// # drop(client); 73 | /// } 74 | /// ``` 75 | /// 76 | pub async fn connect(addr: T) -> crate::Result { 77 | // The `addr` argument is passed directly to `TcpStream::connect`. This 78 | // performs any asynchronous DNS lookup and attempts to establish the TCP 79 | // connection. An error at either step returns an error, which is then 80 | // bubbled up to the caller of `mini_redis` connect. 81 | let socket = TcpStream::connect(addr).await?; 82 | 83 | // Initialize the connection state. This allocates read/write buffers to 84 | // perform redis protocol frame parsing. 85 | let connection = Connection::new(socket); 86 | 87 | Ok(Client { connection }) 88 | } 89 | 90 | /// Ping to the server. 91 | /// 92 | /// Returns PONG if no argument is provided, otherwise 93 | /// return a copy of the argument as a bulk. 94 | /// 95 | /// This command is often used to test if a connection 96 | /// is still alive, or to measure latency. 97 | /// 98 | /// # Examples 99 | /// 100 | /// Demonstrates basic usage. 101 | /// ```no_run 102 | /// use mini_redis::clients::Client; 103 | /// 104 | /// #[tokio::main] 105 | /// async fn main() { 106 | /// let mut client = Client::connect("localhost:6379").await.unwrap(); 107 | /// 108 | /// let pong = client.ping(None).await.unwrap(); 109 | /// assert_eq!(b"PONG", &pong[..]); 110 | /// } 111 | /// ``` 112 | #[instrument(skip(self))] 113 | pub async fn ping(&mut self, msg: Option) -> crate::Result { 114 | let frame = Ping::new(msg).into_frame(); 115 | debug!(request = ?frame); 116 | self.connection.write_frame(&frame).await?; 117 | 118 | match self.read_response().await? { 119 | Frame::Simple(value) => Ok(value.into()), 120 | Frame::Bulk(value) => Ok(value), 121 | frame => Err(frame.to_error()), 122 | } 123 | } 124 | 125 | /// Get the value of key. 126 | /// 127 | /// If the key does not exist the special value `None` is returned. 128 | /// 129 | /// # Examples 130 | /// 131 | /// Demonstrates basic usage. 132 | /// 133 | /// ```no_run 134 | /// use mini_redis::clients::Client; 135 | /// 136 | /// #[tokio::main] 137 | /// async fn main() { 138 | /// let mut client = Client::connect("localhost:6379").await.unwrap(); 139 | /// 140 | /// let val = client.get("foo").await.unwrap(); 141 | /// println!("Got = {:?}", val); 142 | /// } 143 | /// ``` 144 | #[instrument(skip(self))] 145 | pub async fn get(&mut self, key: &str) -> crate::Result> { 146 | // Create a `Get` command for the `key` and convert it to a frame. 147 | let frame = Get::new(key).into_frame(); 148 | 149 | debug!(request = ?frame); 150 | 151 | // Write the frame to the socket. This writes the full frame to the 152 | // socket, waiting if necessary. 153 | self.connection.write_frame(&frame).await?; 154 | 155 | // Wait for the response from the server 156 | // 157 | // Both `Simple` and `Bulk` frames are accepted. `Null` represents the 158 | // key not being present and `None` is returned. 159 | match self.read_response().await? { 160 | Frame::Simple(value) => Ok(Some(value.into())), 161 | Frame::Bulk(value) => Ok(Some(value)), 162 | Frame::Null => Ok(None), 163 | frame => Err(frame.to_error()), 164 | } 165 | } 166 | 167 | /// Set `key` to hold the given `value`. 168 | /// 169 | /// The `value` is associated with `key` until it is overwritten by the next 170 | /// call to `set` or it is removed. 171 | /// 172 | /// If key already holds a value, it is overwritten. Any previous time to 173 | /// live associated with the key is discarded on successful SET operation. 174 | /// 175 | /// # Examples 176 | /// 177 | /// Demonstrates basic usage. 178 | /// 179 | /// ```no_run 180 | /// use mini_redis::clients::Client; 181 | /// 182 | /// #[tokio::main] 183 | /// async fn main() { 184 | /// let mut client = Client::connect("localhost:6379").await.unwrap(); 185 | /// 186 | /// client.set("foo", "bar".into()).await.unwrap(); 187 | /// 188 | /// // Getting the value immediately works 189 | /// let val = client.get("foo").await.unwrap().unwrap(); 190 | /// assert_eq!(val, "bar"); 191 | /// } 192 | /// ``` 193 | #[instrument(skip(self))] 194 | pub async fn set(&mut self, key: &str, value: Bytes) -> crate::Result<()> { 195 | // Create a `Set` command and pass it to `set_cmd`. A separate method is 196 | // used to set a value with an expiration. The common parts of both 197 | // functions are implemented by `set_cmd`. 198 | self.set_cmd(Set::new(key, value, None)).await 199 | } 200 | 201 | /// Set `key` to hold the given `value`. The value expires after `expiration` 202 | /// 203 | /// The `value` is associated with `key` until one of the following: 204 | /// - it expires. 205 | /// - it is overwritten by the next call to `set`. 206 | /// - it is removed. 207 | /// 208 | /// If key already holds a value, it is overwritten. Any previous time to 209 | /// live associated with the key is discarded on a successful SET operation. 210 | /// 211 | /// # Examples 212 | /// 213 | /// Demonstrates basic usage. This example is not **guaranteed** to always 214 | /// work as it relies on time based logic and assumes the client and server 215 | /// stay relatively synchronized in time. The real world tends to not be so 216 | /// favorable. 217 | /// 218 | /// ```no_run 219 | /// use mini_redis::clients::Client; 220 | /// use tokio::time; 221 | /// use std::time::Duration; 222 | /// 223 | /// #[tokio::main] 224 | /// async fn main() { 225 | /// let ttl = Duration::from_millis(500); 226 | /// let mut client = Client::connect("localhost:6379").await.unwrap(); 227 | /// 228 | /// client.set_expires("foo", "bar".into(), ttl).await.unwrap(); 229 | /// 230 | /// // Getting the value immediately works 231 | /// let val = client.get("foo").await.unwrap().unwrap(); 232 | /// assert_eq!(val, "bar"); 233 | /// 234 | /// // Wait for the TTL to expire 235 | /// time::sleep(ttl).await; 236 | /// 237 | /// let val = client.get("foo").await.unwrap(); 238 | /// assert!(val.is_some()); 239 | /// } 240 | /// ``` 241 | #[instrument(skip(self))] 242 | pub async fn set_expires( 243 | &mut self, 244 | key: &str, 245 | value: Bytes, 246 | expiration: Duration, 247 | ) -> crate::Result<()> { 248 | // Create a `Set` command and pass it to `set_cmd`. A separate method is 249 | // used to set a value with an expiration. The common parts of both 250 | // functions are implemented by `set_cmd`. 251 | self.set_cmd(Set::new(key, value, Some(expiration))).await 252 | } 253 | 254 | /// The core `SET` logic, used by both `set` and `set_expires. 255 | async fn set_cmd(&mut self, cmd: Set) -> crate::Result<()> { 256 | // Convert the `Set` command into a frame 257 | let frame = cmd.into_frame(); 258 | 259 | debug!(request = ?frame); 260 | 261 | // Write the frame to the socket. This writes the full frame to the 262 | // socket, waiting if necessary. 263 | self.connection.write_frame(&frame).await?; 264 | 265 | // Wait for the response from the server. On success, the server 266 | // responds simply with `OK`. Any other response indicates an error. 267 | match self.read_response().await? { 268 | Frame::Simple(response) if response == "OK" => Ok(()), 269 | frame => Err(frame.to_error()), 270 | } 271 | } 272 | 273 | /// Posts `message` to the given `channel`. 274 | /// 275 | /// Returns the number of subscribers currently listening on the channel. 276 | /// There is no guarantee that these subscribers receive the message as they 277 | /// may disconnect at any time. 278 | /// 279 | /// # Examples 280 | /// 281 | /// Demonstrates basic usage. 282 | /// 283 | /// ```no_run 284 | /// use mini_redis::clients::Client; 285 | /// 286 | /// #[tokio::main] 287 | /// async fn main() { 288 | /// let mut client = Client::connect("localhost:6379").await.unwrap(); 289 | /// 290 | /// let val = client.publish("foo", "bar".into()).await.unwrap(); 291 | /// println!("Got = {:?}", val); 292 | /// } 293 | /// ``` 294 | #[instrument(skip(self))] 295 | pub async fn publish(&mut self, channel: &str, message: Bytes) -> crate::Result { 296 | // Convert the `Publish` command into a frame 297 | let frame = Publish::new(channel, message).into_frame(); 298 | 299 | debug!(request = ?frame); 300 | 301 | // Write the frame to the socket 302 | self.connection.write_frame(&frame).await?; 303 | 304 | // Read the response 305 | match self.read_response().await? { 306 | Frame::Integer(response) => Ok(response), 307 | frame => Err(frame.to_error()), 308 | } 309 | } 310 | 311 | /// Subscribes the client to the specified channels. 312 | /// 313 | /// Once a client issues a subscribe command, it may no longer issue any 314 | /// non-pub/sub commands. The function consumes `self` and returns a `Subscriber`. 315 | /// 316 | /// The `Subscriber` value is used to receive messages as well as manage the 317 | /// list of channels the client is subscribed to. 318 | #[instrument(skip(self))] 319 | pub async fn subscribe(mut self, channels: Vec) -> crate::Result { 320 | // Issue the subscribe command to the server and wait for confirmation. 321 | // The client will then have been transitioned into the "subscriber" 322 | // state and may only issue pub/sub commands from that point on. 323 | self.subscribe_cmd(&channels).await?; 324 | 325 | // Return the `Subscriber` type 326 | Ok(Subscriber { 327 | client: self, 328 | subscribed_channels: channels, 329 | }) 330 | } 331 | 332 | /// The core `SUBSCRIBE` logic, used by misc subscribe fns 333 | async fn subscribe_cmd(&mut self, channels: &[String]) -> crate::Result<()> { 334 | // Convert the `Subscribe` command into a frame 335 | let frame = Subscribe::new(channels.to_vec()).into_frame(); 336 | 337 | debug!(request = ?frame); 338 | 339 | // Write the frame to the socket 340 | self.connection.write_frame(&frame).await?; 341 | 342 | // For each channel being subscribed to, the server responds with a 343 | // message confirming subscription to that channel. 344 | for channel in channels { 345 | // Read the response 346 | let response = self.read_response().await?; 347 | 348 | // Verify it is confirmation of subscription. 349 | match response { 350 | Frame::Array(ref frame) => match frame.as_slice() { 351 | // The server responds with an array frame in the form of: 352 | // 353 | // ``` 354 | // [ "subscribe", channel, num-subscribed ] 355 | // ``` 356 | // 357 | // where channel is the name of the channel and 358 | // num-subscribed is the number of channels that the client 359 | // is currently subscribed to. 360 | [subscribe, schannel, ..] 361 | if *subscribe == "subscribe" && *schannel == channel => {} 362 | _ => return Err(response.to_error()), 363 | }, 364 | frame => return Err(frame.to_error()), 365 | }; 366 | } 367 | 368 | Ok(()) 369 | } 370 | 371 | /// Reads a response frame from the socket. 372 | /// 373 | /// If an `Error` frame is received, it is converted to `Err`. 374 | async fn read_response(&mut self) -> crate::Result { 375 | let response = self.connection.read_frame().await?; 376 | 377 | debug!(?response); 378 | 379 | match response { 380 | // Error frames are converted to `Err` 381 | Some(Frame::Error(msg)) => Err(msg.into()), 382 | Some(frame) => Ok(frame), 383 | None => { 384 | // Receiving `None` here indicates the server has closed the 385 | // connection without sending a frame. This is unexpected and is 386 | // represented as a "connection reset by peer" error. 387 | let err = Error::new(ErrorKind::ConnectionReset, "connection reset by server"); 388 | 389 | Err(err.into()) 390 | } 391 | } 392 | } 393 | } 394 | 395 | impl Subscriber { 396 | /// Returns the set of channels currently subscribed to. 397 | pub fn get_subscribed(&self) -> &[String] { 398 | &self.subscribed_channels 399 | } 400 | 401 | /// Receive the next message published on a subscribed channel, waiting if 402 | /// necessary. 403 | /// 404 | /// `None` indicates the subscription has been terminated. 405 | pub async fn next_message(&mut self) -> crate::Result> { 406 | match self.client.connection.read_frame().await? { 407 | Some(mframe) => { 408 | debug!(?mframe); 409 | 410 | match mframe { 411 | Frame::Array(ref frame) => match frame.as_slice() { 412 | [message, channel, content] if *message == "message" => Ok(Some(Message { 413 | channel: channel.to_string(), 414 | content: Bytes::from(content.to_string()), 415 | })), 416 | _ => Err(mframe.to_error()), 417 | }, 418 | frame => Err(frame.to_error()), 419 | } 420 | } 421 | None => Ok(None), 422 | } 423 | } 424 | 425 | /// Convert the subscriber into a `Stream` yielding new messages published 426 | /// on subscribed channels. 427 | /// 428 | /// `Subscriber` does not implement stream itself as doing so with safe code 429 | /// is non trivial. The usage of async/await would require a manual Stream 430 | /// implementation to use `unsafe` code. Instead, a conversion function is 431 | /// provided and the returned stream is implemented with the help of the 432 | /// `async-stream` crate. 433 | pub fn into_stream(mut self) -> impl Stream> { 434 | // Uses the `try_stream` macro from the `async-stream` crate. Generators 435 | // are not stable in Rust. The crate uses a macro to simulate generators 436 | // on top of async/await. There are limitations, so read the 437 | // documentation there. 438 | try_stream! { 439 | while let Some(message) = self.next_message().await? { 440 | yield message; 441 | } 442 | } 443 | } 444 | 445 | /// Subscribe to a list of new channels 446 | #[instrument(skip(self))] 447 | pub async fn subscribe(&mut self, channels: &[String]) -> crate::Result<()> { 448 | // Issue the subscribe command 449 | self.client.subscribe_cmd(channels).await?; 450 | 451 | // Update the set of subscribed channels. 452 | self.subscribed_channels 453 | .extend(channels.iter().map(Clone::clone)); 454 | 455 | Ok(()) 456 | } 457 | 458 | /// Unsubscribe to a list of new channels 459 | #[instrument(skip(self))] 460 | pub async fn unsubscribe(&mut self, channels: &[String]) -> crate::Result<()> { 461 | let frame = Unsubscribe::new(channels).into_frame(); 462 | 463 | debug!(request = ?frame); 464 | 465 | // Write the frame to the socket 466 | self.client.connection.write_frame(&frame).await?; 467 | 468 | // if the input channel list is empty, server acknowledges as unsubscribing 469 | // from all subscribed channels, so we assert that the unsubscribe list received 470 | // matches the client subscribed one 471 | let num = if channels.is_empty() { 472 | self.subscribed_channels.len() 473 | } else { 474 | channels.len() 475 | }; 476 | 477 | // Read the response 478 | for _ in 0..num { 479 | let response = self.client.read_response().await?; 480 | 481 | match response { 482 | Frame::Array(ref frame) => match frame.as_slice() { 483 | [unsubscribe, channel, ..] if *unsubscribe == "unsubscribe" => { 484 | let len = self.subscribed_channels.len(); 485 | 486 | if len == 0 { 487 | // There must be at least one channel 488 | return Err(response.to_error()); 489 | } 490 | 491 | // unsubscribed channel should exist in the subscribed list at this point 492 | self.subscribed_channels.retain(|c| *channel != &c[..]); 493 | 494 | // Only a single channel should be removed from the 495 | // list of subscribed channels. 496 | if self.subscribed_channels.len() != len - 1 { 497 | return Err(response.to_error()); 498 | } 499 | } 500 | _ => return Err(response.to_error()), 501 | }, 502 | frame => return Err(frame.to_error()), 503 | }; 504 | } 505 | 506 | Ok(()) 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /src/clients/mod.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | pub use client::{Client, Message, Subscriber}; 3 | 4 | mod blocking_client; 5 | pub use blocking_client::BlockingClient; 6 | 7 | mod buffered_client; 8 | pub use buffered_client::BufferedClient; 9 | -------------------------------------------------------------------------------- /src/cmd/get.rs: -------------------------------------------------------------------------------- 1 | use crate::{Connection, Db, Frame, Parse}; 2 | 3 | use bytes::Bytes; 4 | use tracing::{debug, instrument}; 5 | 6 | /// Get the value of key. 7 | /// 8 | /// If the key does not exist the special value nil is returned. An error is 9 | /// returned if the value stored at key is not a string, because GET only 10 | /// handles string values. 11 | #[derive(Debug)] 12 | pub struct Get { 13 | /// Name of the key to get 14 | key: String, 15 | } 16 | 17 | impl Get { 18 | /// Create a new `Get` command which fetches `key`. 19 | pub fn new(key: impl ToString) -> Get { 20 | Get { 21 | key: key.to_string(), 22 | } 23 | } 24 | 25 | /// Get the key 26 | pub fn key(&self) -> &str { 27 | &self.key 28 | } 29 | 30 | /// Parse a `Get` instance from a received frame. 31 | /// 32 | /// The `Parse` argument provides a cursor-like API to read fields from the 33 | /// `Frame`. At this point, the entire frame has already been received from 34 | /// the socket. 35 | /// 36 | /// The `GET` string has already been consumed. 37 | /// 38 | /// # Returns 39 | /// 40 | /// Returns the `Get` value on success. If the frame is malformed, `Err` is 41 | /// returned. 42 | /// 43 | /// # Format 44 | /// 45 | /// Expects an array frame containing two entries. 46 | /// 47 | /// ```text 48 | /// GET key 49 | /// ``` 50 | pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result { 51 | // The `GET` string has already been consumed. The next value is the 52 | // name of the key to get. If the next value is not a string or the 53 | // input is fully consumed, then an error is returned. 54 | let key = parse.next_string()?; 55 | 56 | Ok(Get { key }) 57 | } 58 | 59 | /// Apply the `Get` command to the specified `Db` instance. 60 | /// 61 | /// The response is written to `dst`. This is called by the server in order 62 | /// to execute a received command. 63 | #[instrument(skip(self, db, dst))] 64 | pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> { 65 | // Get the value from the shared database state 66 | let response = if let Some(value) = db.get(&self.key) { 67 | // If a value is present, it is written to the client in "bulk" 68 | // format. 69 | Frame::Bulk(value) 70 | } else { 71 | // If there is no value, `Null` is written. 72 | Frame::Null 73 | }; 74 | 75 | debug!(?response); 76 | 77 | // Write the response back to the client 78 | dst.write_frame(&response).await?; 79 | 80 | Ok(()) 81 | } 82 | 83 | /// Converts the command into an equivalent `Frame`. 84 | /// 85 | /// This is called by the client when encoding a `Get` command to send to 86 | /// the server. 87 | pub(crate) fn into_frame(self) -> Frame { 88 | let mut frame = Frame::array(); 89 | frame.push_bulk(Bytes::from("get".as_bytes())); 90 | frame.push_bulk(Bytes::from(self.key.into_bytes())); 91 | frame 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | mod get; 2 | pub use get::Get; 3 | 4 | mod publish; 5 | pub use publish::Publish; 6 | 7 | mod set; 8 | pub use set::Set; 9 | 10 | mod subscribe; 11 | pub use subscribe::{Subscribe, Unsubscribe}; 12 | 13 | mod ping; 14 | pub use ping::Ping; 15 | 16 | mod unknown; 17 | pub use unknown::Unknown; 18 | 19 | use crate::{Connection, Db, Frame, Parse, ParseError, Shutdown}; 20 | 21 | /// Enumeration of supported Redis commands. 22 | /// 23 | /// Methods called on `Command` are delegated to the command implementation. 24 | #[derive(Debug)] 25 | pub enum Command { 26 | Get(Get), 27 | Publish(Publish), 28 | Set(Set), 29 | Subscribe(Subscribe), 30 | Unsubscribe(Unsubscribe), 31 | Ping(Ping), 32 | Unknown(Unknown), 33 | } 34 | 35 | impl Command { 36 | /// Parse a command from a received frame. 37 | /// 38 | /// The `Frame` must represent a Redis command supported by `mini-redis` and 39 | /// be the array variant. 40 | /// 41 | /// # Returns 42 | /// 43 | /// On success, the command value is returned, otherwise, `Err` is returned. 44 | pub fn from_frame(frame: Frame) -> crate::Result { 45 | // The frame value is decorated with `Parse`. `Parse` provides a 46 | // "cursor" like API which makes parsing the command easier. 47 | // 48 | // The frame value must be an array variant. Any other frame variants 49 | // result in an error being returned. 50 | let mut parse = Parse::new(frame)?; 51 | 52 | // All redis commands begin with the command name as a string. The name 53 | // is read and converted to lower cases in order to do case sensitive 54 | // matching. 55 | let command_name = parse.next_string()?.to_lowercase(); 56 | 57 | // Match the command name, delegating the rest of the parsing to the 58 | // specific command. 59 | let command = match &command_name[..] { 60 | "get" => Command::Get(Get::parse_frames(&mut parse)?), 61 | "publish" => Command::Publish(Publish::parse_frames(&mut parse)?), 62 | "set" => Command::Set(Set::parse_frames(&mut parse)?), 63 | "subscribe" => Command::Subscribe(Subscribe::parse_frames(&mut parse)?), 64 | "unsubscribe" => Command::Unsubscribe(Unsubscribe::parse_frames(&mut parse)?), 65 | "ping" => Command::Ping(Ping::parse_frames(&mut parse)?), 66 | _ => { 67 | // The command is not recognized and an Unknown command is 68 | // returned. 69 | // 70 | // `return` is called here to skip the `finish()` call below. As 71 | // the command is not recognized, there is most likely 72 | // unconsumed fields remaining in the `Parse` instance. 73 | return Ok(Command::Unknown(Unknown::new(command_name))); 74 | } 75 | }; 76 | 77 | // Check if there is any remaining unconsumed fields in the `Parse` 78 | // value. If fields remain, this indicates an unexpected frame format 79 | // and an error is returned. 80 | parse.finish()?; 81 | 82 | // The command has been successfully parsed 83 | Ok(command) 84 | } 85 | 86 | /// Apply the command to the specified `Db` instance. 87 | /// 88 | /// The response is written to `dst`. This is called by the server in order 89 | /// to execute a received command. 90 | pub(crate) async fn apply( 91 | self, 92 | db: &Db, 93 | dst: &mut Connection, 94 | shutdown: &mut Shutdown, 95 | ) -> crate::Result<()> { 96 | use Command::*; 97 | 98 | match self { 99 | Get(cmd) => cmd.apply(db, dst).await, 100 | Publish(cmd) => cmd.apply(db, dst).await, 101 | Set(cmd) => cmd.apply(db, dst).await, 102 | Subscribe(cmd) => cmd.apply(db, dst, shutdown).await, 103 | Ping(cmd) => cmd.apply(dst).await, 104 | Unknown(cmd) => cmd.apply(dst).await, 105 | // `Unsubscribe` cannot be applied. It may only be received from the 106 | // context of a `Subscribe` command. 107 | Unsubscribe(_) => Err("`Unsubscribe` is unsupported in this context".into()), 108 | } 109 | } 110 | 111 | /// Returns the command name 112 | pub(crate) fn get_name(&self) -> &str { 113 | match self { 114 | Command::Get(_) => "get", 115 | Command::Publish(_) => "pub", 116 | Command::Set(_) => "set", 117 | Command::Subscribe(_) => "subscribe", 118 | Command::Unsubscribe(_) => "unsubscribe", 119 | Command::Ping(_) => "ping", 120 | Command::Unknown(cmd) => cmd.get_name(), 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/cmd/ping.rs: -------------------------------------------------------------------------------- 1 | use crate::{Connection, Frame, Parse, ParseError}; 2 | use bytes::Bytes; 3 | use tracing::{debug, instrument}; 4 | 5 | /// Returns PONG if no argument is provided, otherwise 6 | /// return a copy of the argument as a bulk. 7 | /// 8 | /// This command is often used to test if a connection 9 | /// is still alive, or to measure latency. 10 | #[derive(Debug, Default)] 11 | pub struct Ping { 12 | /// optional message to be returned 13 | msg: Option, 14 | } 15 | 16 | impl Ping { 17 | /// Create a new `Ping` command with optional `msg`. 18 | pub fn new(msg: Option) -> Ping { 19 | Ping { msg } 20 | } 21 | 22 | /// Parse a `Ping` instance from a received frame. 23 | /// 24 | /// The `Parse` argument provides a cursor-like API to read fields from the 25 | /// `Frame`. At this point, the entire frame has already been received from 26 | /// the socket. 27 | /// 28 | /// The `PING` string has already been consumed. 29 | /// 30 | /// # Returns 31 | /// 32 | /// Returns the `Ping` value on success. If the frame is malformed, `Err` is 33 | /// returned. 34 | /// 35 | /// # Format 36 | /// 37 | /// Expects an array frame containing `PING` and an optional message. 38 | /// 39 | /// ```text 40 | /// PING [message] 41 | /// ``` 42 | pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result { 43 | match parse.next_bytes() { 44 | Ok(msg) => Ok(Ping::new(Some(msg))), 45 | Err(ParseError::EndOfStream) => Ok(Ping::default()), 46 | Err(e) => Err(e.into()), 47 | } 48 | } 49 | 50 | /// Apply the `Ping` command and return the message. 51 | /// 52 | /// The response is written to `dst`. This is called by the server in order 53 | /// to execute a received command. 54 | #[instrument(skip(self, dst))] 55 | pub(crate) async fn apply(self, dst: &mut Connection) -> crate::Result<()> { 56 | let response = match self.msg { 57 | None => Frame::Simple("PONG".to_string()), 58 | Some(msg) => Frame::Bulk(msg), 59 | }; 60 | 61 | debug!(?response); 62 | 63 | // Write the response back to the client 64 | dst.write_frame(&response).await?; 65 | 66 | Ok(()) 67 | } 68 | 69 | /// Converts the command into an equivalent `Frame`. 70 | /// 71 | /// This is called by the client when encoding a `Ping` command to send 72 | /// to the server. 73 | pub(crate) fn into_frame(self) -> Frame { 74 | let mut frame = Frame::array(); 75 | frame.push_bulk(Bytes::from("ping".as_bytes())); 76 | if let Some(msg) = self.msg { 77 | frame.push_bulk(msg); 78 | } 79 | frame 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/cmd/publish.rs: -------------------------------------------------------------------------------- 1 | use crate::{Connection, Db, Frame, Parse}; 2 | 3 | use bytes::Bytes; 4 | 5 | /// Posts a message to the given channel. 6 | /// 7 | /// Send a message into a channel without any knowledge of individual consumers. 8 | /// Consumers may subscribe to channels in order to receive the messages. 9 | /// 10 | /// Channel names have no relation to the key-value namespace. Publishing on a 11 | /// channel named "foo" has no relation to setting the "foo" key. 12 | #[derive(Debug)] 13 | pub struct Publish { 14 | /// Name of the channel on which the message should be published. 15 | channel: String, 16 | 17 | /// The message to publish. 18 | message: Bytes, 19 | } 20 | 21 | impl Publish { 22 | /// Create a new `Publish` command which sends `message` on `channel`. 23 | pub(crate) fn new(channel: impl ToString, message: Bytes) -> Publish { 24 | Publish { 25 | channel: channel.to_string(), 26 | message, 27 | } 28 | } 29 | 30 | /// Parse a `Publish` instance from a received frame. 31 | /// 32 | /// The `Parse` argument provides a cursor-like API to read fields from the 33 | /// `Frame`. At this point, the entire frame has already been received from 34 | /// the socket. 35 | /// 36 | /// The `PUBLISH` string has already been consumed. 37 | /// 38 | /// # Returns 39 | /// 40 | /// On success, the `Publish` value is returned. If the frame is malformed, 41 | /// `Err` is returned. 42 | /// 43 | /// # Format 44 | /// 45 | /// Expects an array frame containing three entries. 46 | /// 47 | /// ```text 48 | /// PUBLISH channel message 49 | /// ``` 50 | pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result { 51 | // The `PUBLISH` string has already been consumed. Extract the `channel` 52 | // and `message` values from the frame. 53 | // 54 | // The `channel` must be a valid string. 55 | let channel = parse.next_string()?; 56 | 57 | // The `message` is arbitrary bytes. 58 | let message = parse.next_bytes()?; 59 | 60 | Ok(Publish { channel, message }) 61 | } 62 | 63 | /// Apply the `Publish` command to the specified `Db` instance. 64 | /// 65 | /// The response is written to `dst`. This is called by the server in order 66 | /// to execute a received command. 67 | pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> { 68 | // The shared state contains the `tokio::sync::broadcast::Sender` for 69 | // all active channels. Calling `db.publish` dispatches the message into 70 | // the appropriate channel. 71 | // 72 | // The number of subscribers currently listening on the channel is 73 | // returned. This does not mean that `num_subscriber` channels will 74 | // receive the message. Subscribers may drop before receiving the 75 | // message. Given this, `num_subscribers` should only be used as a 76 | // "hint". 77 | let num_subscribers = db.publish(&self.channel, self.message); 78 | 79 | // The number of subscribers is returned as the response to the publish 80 | // request. 81 | let response = Frame::Integer(num_subscribers as u64); 82 | 83 | // Write the frame to the client. 84 | dst.write_frame(&response).await?; 85 | 86 | Ok(()) 87 | } 88 | 89 | /// Converts the command into an equivalent `Frame`. 90 | /// 91 | /// This is called by the client when encoding a `Publish` command to send 92 | /// to the server. 93 | pub(crate) fn into_frame(self) -> Frame { 94 | let mut frame = Frame::array(); 95 | frame.push_bulk(Bytes::from("publish".as_bytes())); 96 | frame.push_bulk(Bytes::from(self.channel.into_bytes())); 97 | frame.push_bulk(self.message); 98 | 99 | frame 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/cmd/set.rs: -------------------------------------------------------------------------------- 1 | use crate::cmd::{Parse, ParseError}; 2 | use crate::{Connection, Db, Frame}; 3 | 4 | use bytes::Bytes; 5 | use std::time::Duration; 6 | use tracing::{debug, instrument}; 7 | 8 | /// Set `key` to hold the string `value`. 9 | /// 10 | /// If `key` already holds a value, it is overwritten, regardless of its type. 11 | /// Any previous time to live associated with the key is discarded on successful 12 | /// SET operation. 13 | /// 14 | /// # Options 15 | /// 16 | /// Currently, the following options are supported: 17 | /// 18 | /// * EX `seconds` -- Set the specified expire time, in seconds. 19 | /// * PX `milliseconds` -- Set the specified expire time, in milliseconds. 20 | #[derive(Debug)] 21 | pub struct Set { 22 | /// the lookup key 23 | key: String, 24 | 25 | /// the value to be stored 26 | value: Bytes, 27 | 28 | /// When to expire the key 29 | expire: Option, 30 | } 31 | 32 | impl Set { 33 | /// Create a new `Set` command which sets `key` to `value`. 34 | /// 35 | /// If `expire` is `Some`, the value should expire after the specified 36 | /// duration. 37 | pub fn new(key: impl ToString, value: Bytes, expire: Option) -> Set { 38 | Set { 39 | key: key.to_string(), 40 | value, 41 | expire, 42 | } 43 | } 44 | 45 | /// Get the key 46 | pub fn key(&self) -> &str { 47 | &self.key 48 | } 49 | 50 | /// Get the value 51 | pub fn value(&self) -> &Bytes { 52 | &self.value 53 | } 54 | 55 | /// Get the expire 56 | pub fn expire(&self) -> Option { 57 | self.expire 58 | } 59 | 60 | /// Parse a `Set` instance from a received frame. 61 | /// 62 | /// The `Parse` argument provides a cursor-like API to read fields from the 63 | /// `Frame`. At this point, the entire frame has already been received from 64 | /// the socket. 65 | /// 66 | /// The `SET` string has already been consumed. 67 | /// 68 | /// # Returns 69 | /// 70 | /// Returns the `Set` value on success. If the frame is malformed, `Err` is 71 | /// returned. 72 | /// 73 | /// # Format 74 | /// 75 | /// Expects an array frame containing at least 3 entries. 76 | /// 77 | /// ```text 78 | /// SET key value [EX seconds|PX milliseconds] 79 | /// ``` 80 | pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result { 81 | use ParseError::EndOfStream; 82 | 83 | // Read the key to set. This is a required field 84 | let key = parse.next_string()?; 85 | 86 | // Read the value to set. This is a required field. 87 | let value = parse.next_bytes()?; 88 | 89 | // The expiration is optional. If nothing else follows, then it is 90 | // `None`. 91 | let mut expire = None; 92 | 93 | // Attempt to parse another string. 94 | match parse.next_string() { 95 | Ok(s) if s.to_uppercase() == "EX" => { 96 | // An expiration is specified in seconds. The next value is an 97 | // integer. 98 | let secs = parse.next_int()?; 99 | expire = Some(Duration::from_secs(secs)); 100 | } 101 | Ok(s) if s.to_uppercase() == "PX" => { 102 | // An expiration is specified in milliseconds. The next value is 103 | // an integer. 104 | let ms = parse.next_int()?; 105 | expire = Some(Duration::from_millis(ms)); 106 | } 107 | // Currently, mini-redis does not support any of the other SET 108 | // options. An error here results in the connection being 109 | // terminated. Other connections will continue to operate normally. 110 | Ok(_) => return Err("currently `SET` only supports the expiration option".into()), 111 | // The `EndOfStream` error indicates there is no further data to 112 | // parse. In this case, it is a normal run time situation and 113 | // indicates there are no specified `SET` options. 114 | Err(EndOfStream) => {} 115 | // All other errors are bubbled up, resulting in the connection 116 | // being terminated. 117 | Err(err) => return Err(err.into()), 118 | } 119 | 120 | Ok(Set { key, value, expire }) 121 | } 122 | 123 | /// Apply the `Set` command to the specified `Db` instance. 124 | /// 125 | /// The response is written to `dst`. This is called by the server in order 126 | /// to execute a received command. 127 | #[instrument(skip(self, db, dst))] 128 | pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> { 129 | // Set the value in the shared database state. 130 | db.set(self.key, self.value, self.expire); 131 | 132 | // Create a success response and write it to `dst`. 133 | let response = Frame::Simple("OK".to_string()); 134 | debug!(?response); 135 | dst.write_frame(&response).await?; 136 | 137 | Ok(()) 138 | } 139 | 140 | /// Converts the command into an equivalent `Frame`. 141 | /// 142 | /// This is called by the client when encoding a `Set` command to send to 143 | /// the server. 144 | pub(crate) fn into_frame(self) -> Frame { 145 | let mut frame = Frame::array(); 146 | frame.push_bulk(Bytes::from("set".as_bytes())); 147 | frame.push_bulk(Bytes::from(self.key.into_bytes())); 148 | frame.push_bulk(self.value); 149 | if let Some(ms) = self.expire { 150 | // Expirations in Redis protocol can be specified in two ways 151 | // 1. SET key value EX seconds 152 | // 2. SET key value PX milliseconds 153 | // We the second option because it allows greater precision and 154 | // src/bin/cli.rs parses the expiration argument as milliseconds 155 | // in duration_from_ms_str() 156 | frame.push_bulk(Bytes::from("px".as_bytes())); 157 | frame.push_int(ms.as_millis() as u64); 158 | } 159 | frame 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/cmd/subscribe.rs: -------------------------------------------------------------------------------- 1 | use crate::cmd::{Parse, ParseError, Unknown}; 2 | use crate::{Command, Connection, Db, Frame, Shutdown}; 3 | 4 | use bytes::Bytes; 5 | use std::pin::Pin; 6 | use tokio::select; 7 | use tokio::sync::broadcast; 8 | use tokio_stream::{Stream, StreamExt, StreamMap}; 9 | 10 | /// Subscribes the client to one or more channels. 11 | /// 12 | /// Once the client enters the subscribed state, it is not supposed to issue any 13 | /// other commands, except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, 14 | /// PUNSUBSCRIBE, PING and QUIT commands. 15 | #[derive(Debug)] 16 | pub struct Subscribe { 17 | channels: Vec, 18 | } 19 | 20 | /// Unsubscribes the client from one or more channels. 21 | /// 22 | /// When no channels are specified, the client is unsubscribed from all the 23 | /// previously subscribed channels. 24 | #[derive(Clone, Debug)] 25 | pub struct Unsubscribe { 26 | channels: Vec, 27 | } 28 | 29 | /// Stream of messages. The stream receives messages from the 30 | /// `broadcast::Receiver`. We use `stream!` to create a `Stream` that consumes 31 | /// messages. Because `stream!` values cannot be named, we box the stream using 32 | /// a trait object. 33 | type Messages = Pin + Send>>; 34 | 35 | impl Subscribe { 36 | /// Creates a new `Subscribe` command to listen on the specified channels. 37 | pub(crate) fn new(channels: Vec) -> Subscribe { 38 | Subscribe { channels } 39 | } 40 | 41 | /// Parse a `Subscribe` instance from a received frame. 42 | /// 43 | /// The `Parse` argument provides a cursor-like API to read fields from the 44 | /// `Frame`. At this point, the entire frame has already been received from 45 | /// the socket. 46 | /// 47 | /// The `SUBSCRIBE` string has already been consumed. 48 | /// 49 | /// # Returns 50 | /// 51 | /// On success, the `Subscribe` value is returned. If the frame is 52 | /// malformed, `Err` is returned. 53 | /// 54 | /// # Format 55 | /// 56 | /// Expects an array frame containing two or more entries. 57 | /// 58 | /// ```text 59 | /// SUBSCRIBE channel [channel ...] 60 | /// ``` 61 | pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result { 62 | use ParseError::EndOfStream; 63 | 64 | // The `SUBSCRIBE` string has already been consumed. At this point, 65 | // there is one or more strings remaining in `parse`. These represent 66 | // the channels to subscribe to. 67 | // 68 | // Extract the first string. If there is none, the the frame is 69 | // malformed and the error is bubbled up. 70 | let mut channels = vec![parse.next_string()?]; 71 | 72 | // Now, the remainder of the frame is consumed. Each value must be a 73 | // string or the frame is malformed. Once all values in the frame have 74 | // been consumed, the command is fully parsed. 75 | loop { 76 | match parse.next_string() { 77 | // A string has been consumed from the `parse`, push it into the 78 | // list of channels to subscribe to. 79 | Ok(s) => channels.push(s), 80 | // The `EndOfStream` error indicates there is no further data to 81 | // parse. 82 | Err(EndOfStream) => break, 83 | // All other errors are bubbled up, resulting in the connection 84 | // being terminated. 85 | Err(err) => return Err(err.into()), 86 | } 87 | } 88 | 89 | Ok(Subscribe { channels }) 90 | } 91 | 92 | /// Apply the `Subscribe` command to the specified `Db` instance. 93 | /// 94 | /// This function is the entry point and includes the initial list of 95 | /// channels to subscribe to. Additional `subscribe` and `unsubscribe` 96 | /// commands may be received from the client and the list of subscriptions 97 | /// are updated accordingly. 98 | /// 99 | /// [here]: https://redis.io/topics/pubsub 100 | pub(crate) async fn apply( 101 | mut self, 102 | db: &Db, 103 | dst: &mut Connection, 104 | shutdown: &mut Shutdown, 105 | ) -> crate::Result<()> { 106 | // Each individual channel subscription is handled using a 107 | // `sync::broadcast` channel. Messages are then fanned out to all 108 | // clients currently subscribed to the channels. 109 | // 110 | // An individual client may subscribe to multiple channels and may 111 | // dynamically add and remove channels from its subscription set. To 112 | // handle this, a `StreamMap` is used to track active subscriptions. The 113 | // `StreamMap` merges messages from individual broadcast channels as 114 | // they are received. 115 | let mut subscriptions = StreamMap::new(); 116 | 117 | loop { 118 | // `self.channels` is used to track additional channels to subscribe 119 | // to. When new `SUBSCRIBE` commands are received during the 120 | // execution of `apply`, the new channels are pushed onto this vec. 121 | for channel_name in self.channels.drain(..) { 122 | subscribe_to_channel(channel_name, &mut subscriptions, db, dst).await?; 123 | } 124 | 125 | // Wait for one of the following to happen: 126 | // 127 | // - Receive a message from one of the subscribed channels. 128 | // - Receive a subscribe or unsubscribe command from the client. 129 | // - A server shutdown signal. 130 | select! { 131 | // Receive messages from subscribed channels 132 | Some((channel_name, msg)) = subscriptions.next() => { 133 | dst.write_frame(&make_message_frame(channel_name, msg)).await?; 134 | } 135 | res = dst.read_frame() => { 136 | let frame = match res? { 137 | Some(frame) => frame, 138 | // This happens if the remote client has disconnected. 139 | None => return Ok(()) 140 | }; 141 | 142 | handle_command( 143 | frame, 144 | &mut self.channels, 145 | &mut subscriptions, 146 | dst, 147 | ).await?; 148 | } 149 | _ = shutdown.recv() => { 150 | return Ok(()); 151 | } 152 | }; 153 | } 154 | } 155 | 156 | /// Converts the command into an equivalent `Frame`. 157 | /// 158 | /// This is called by the client when encoding a `Subscribe` command to send 159 | /// to the server. 160 | pub(crate) fn into_frame(self) -> Frame { 161 | let mut frame = Frame::array(); 162 | frame.push_bulk(Bytes::from("subscribe".as_bytes())); 163 | for channel in self.channels { 164 | frame.push_bulk(Bytes::from(channel.into_bytes())); 165 | } 166 | frame 167 | } 168 | } 169 | 170 | async fn subscribe_to_channel( 171 | channel_name: String, 172 | subscriptions: &mut StreamMap, 173 | db: &Db, 174 | dst: &mut Connection, 175 | ) -> crate::Result<()> { 176 | let mut rx = db.subscribe(channel_name.clone()); 177 | 178 | // Subscribe to the channel. 179 | let rx = Box::pin(async_stream::stream! { 180 | loop { 181 | match rx.recv().await { 182 | Ok(msg) => yield msg, 183 | // If we lagged in consuming messages, just resume. 184 | Err(broadcast::error::RecvError::Lagged(_)) => {} 185 | Err(_) => break, 186 | } 187 | } 188 | }); 189 | 190 | // Track subscription in this client's subscription set. 191 | subscriptions.insert(channel_name.clone(), rx); 192 | 193 | // Respond with the successful subscription 194 | let response = make_subscribe_frame(channel_name, subscriptions.len()); 195 | dst.write_frame(&response).await?; 196 | 197 | Ok(()) 198 | } 199 | 200 | /// Handle a command received while inside `Subscribe::apply`. Only subscribe 201 | /// and unsubscribe commands are permitted in this context. 202 | /// 203 | /// Any new subscriptions are appended to `subscribe_to` instead of modifying 204 | /// `subscriptions`. 205 | async fn handle_command( 206 | frame: Frame, 207 | subscribe_to: &mut Vec, 208 | subscriptions: &mut StreamMap, 209 | dst: &mut Connection, 210 | ) -> crate::Result<()> { 211 | // A command has been received from the client. 212 | // 213 | // Only `SUBSCRIBE` and `UNSUBSCRIBE` commands are permitted 214 | // in this context. 215 | match Command::from_frame(frame)? { 216 | Command::Subscribe(subscribe) => { 217 | // The `apply` method will subscribe to the channels we add to this 218 | // vector. 219 | subscribe_to.extend(subscribe.channels.into_iter()); 220 | } 221 | Command::Unsubscribe(mut unsubscribe) => { 222 | // If no channels are specified, this requests unsubscribing from 223 | // **all** channels. To implement this, the `unsubscribe.channels` 224 | // vec is populated with the list of channels currently subscribed 225 | // to. 226 | if unsubscribe.channels.is_empty() { 227 | unsubscribe.channels = subscriptions 228 | .keys() 229 | .map(|channel_name| channel_name.to_string()) 230 | .collect(); 231 | } 232 | 233 | for channel_name in unsubscribe.channels { 234 | subscriptions.remove(&channel_name); 235 | 236 | let response = make_unsubscribe_frame(channel_name, subscriptions.len()); 237 | dst.write_frame(&response).await?; 238 | } 239 | } 240 | command => { 241 | let cmd = Unknown::new(command.get_name()); 242 | cmd.apply(dst).await?; 243 | } 244 | } 245 | Ok(()) 246 | } 247 | 248 | /// Creates the response to a subscribe request. 249 | /// 250 | /// All of these functions take the `channel_name` as a `String` instead of 251 | /// a `&str` since `Bytes::from` can reuse the allocation in the `String`, and 252 | /// taking a `&str` would require copying the data. This allows the caller to 253 | /// decide whether to clone the channel name or not. 254 | fn make_subscribe_frame(channel_name: String, num_subs: usize) -> Frame { 255 | let mut response = Frame::array(); 256 | response.push_bulk(Bytes::from_static(b"subscribe")); 257 | response.push_bulk(Bytes::from(channel_name)); 258 | response.push_int(num_subs as u64); 259 | response 260 | } 261 | 262 | /// Creates the response to an unsubcribe request. 263 | fn make_unsubscribe_frame(channel_name: String, num_subs: usize) -> Frame { 264 | let mut response = Frame::array(); 265 | response.push_bulk(Bytes::from_static(b"unsubscribe")); 266 | response.push_bulk(Bytes::from(channel_name)); 267 | response.push_int(num_subs as u64); 268 | response 269 | } 270 | 271 | /// Creates a message informing the client about a new message on a channel that 272 | /// the client subscribes to. 273 | fn make_message_frame(channel_name: String, msg: Bytes) -> Frame { 274 | let mut response = Frame::array(); 275 | response.push_bulk(Bytes::from_static(b"message")); 276 | response.push_bulk(Bytes::from(channel_name)); 277 | response.push_bulk(msg); 278 | response 279 | } 280 | 281 | impl Unsubscribe { 282 | /// Create a new `Unsubscribe` command with the given `channels`. 283 | pub(crate) fn new(channels: &[String]) -> Unsubscribe { 284 | Unsubscribe { 285 | channels: channels.to_vec(), 286 | } 287 | } 288 | 289 | /// Parse an `Unsubscribe` instance from a received frame. 290 | /// 291 | /// The `Parse` argument provides a cursor-like API to read fields from the 292 | /// `Frame`. At this point, the entire frame has already been received from 293 | /// the socket. 294 | /// 295 | /// The `UNSUBSCRIBE` string has already been consumed. 296 | /// 297 | /// # Returns 298 | /// 299 | /// On success, the `Unsubscribe` value is returned. If the frame is 300 | /// malformed, `Err` is returned. 301 | /// 302 | /// # Format 303 | /// 304 | /// Expects an array frame containing at least one entry. 305 | /// 306 | /// ```text 307 | /// UNSUBSCRIBE [channel [channel ...]] 308 | /// ``` 309 | pub(crate) fn parse_frames(parse: &mut Parse) -> Result { 310 | use ParseError::EndOfStream; 311 | 312 | // There may be no channels listed, so start with an empty vec. 313 | let mut channels = vec![]; 314 | 315 | // Each entry in the frame must be a string or the frame is malformed. 316 | // Once all values in the frame have been consumed, the command is fully 317 | // parsed. 318 | loop { 319 | match parse.next_string() { 320 | // A string has been consumed from the `parse`, push it into the 321 | // list of channels to unsubscribe from. 322 | Ok(s) => channels.push(s), 323 | // The `EndOfStream` error indicates there is no further data to 324 | // parse. 325 | Err(EndOfStream) => break, 326 | // All other errors are bubbled up, resulting in the connection 327 | // being terminated. 328 | Err(err) => return Err(err), 329 | } 330 | } 331 | 332 | Ok(Unsubscribe { channels }) 333 | } 334 | 335 | /// Converts the command into an equivalent `Frame`. 336 | /// 337 | /// This is called by the client when encoding an `Unsubscribe` command to 338 | /// send to the server. 339 | pub(crate) fn into_frame(self) -> Frame { 340 | let mut frame = Frame::array(); 341 | frame.push_bulk(Bytes::from("unsubscribe".as_bytes())); 342 | 343 | for channel in self.channels { 344 | frame.push_bulk(Bytes::from(channel.into_bytes())); 345 | } 346 | 347 | frame 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/cmd/unknown.rs: -------------------------------------------------------------------------------- 1 | use crate::{Connection, Frame}; 2 | 3 | use tracing::{debug, instrument}; 4 | 5 | /// Represents an "unknown" command. This is not a real `Redis` command. 6 | #[derive(Debug)] 7 | pub struct Unknown { 8 | command_name: String, 9 | } 10 | 11 | impl Unknown { 12 | /// Create a new `Unknown` command which responds to unknown commands 13 | /// issued by clients 14 | pub(crate) fn new(key: impl ToString) -> Unknown { 15 | Unknown { 16 | command_name: key.to_string(), 17 | } 18 | } 19 | 20 | /// Returns the command name 21 | pub(crate) fn get_name(&self) -> &str { 22 | &self.command_name 23 | } 24 | 25 | /// Responds to the client, indicating the command is not recognized. 26 | /// 27 | /// This usually means the command is not yet implemented by `mini-redis`. 28 | #[instrument(skip(self, dst))] 29 | pub(crate) async fn apply(self, dst: &mut Connection) -> crate::Result<()> { 30 | let response = Frame::Error(format!("ERR unknown command '{}'", self.command_name)); 31 | 32 | debug!(?response); 33 | 34 | dst.write_frame(&response).await?; 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/connection.rs: -------------------------------------------------------------------------------- 1 | use crate::frame::{self, Frame}; 2 | 3 | use bytes::{Buf, BytesMut}; 4 | use std::io::{self, Cursor}; 5 | use tokio::io::{AsyncReadExt, AsyncWriteExt, BufWriter}; 6 | use tokio::net::TcpStream; 7 | 8 | /// Send and receive `Frame` values from a remote peer. 9 | /// 10 | /// When implementing networking protocols, a message on that protocol is 11 | /// often composed of several smaller messages known as frames. The purpose of 12 | /// `Connection` is to read and write frames on the underlying `TcpStream`. 13 | /// 14 | /// To read frames, the `Connection` uses an internal buffer, which is filled 15 | /// up until there are enough bytes to create a full frame. Once this happens, 16 | /// the `Connection` creates the frame and returns it to the caller. 17 | /// 18 | /// When sending frames, the frame is first encoded into the write buffer. 19 | /// The contents of the write buffer are then written to the socket. 20 | #[derive(Debug)] 21 | pub struct Connection { 22 | // The `TcpStream`. It is decorated with a `BufWriter`, which provides write 23 | // level buffering. The `BufWriter` implementation provided by Tokio is 24 | // sufficient for our needs. 25 | stream: BufWriter, 26 | 27 | // The buffer for reading frames. 28 | buffer: BytesMut, 29 | } 30 | 31 | impl Connection { 32 | /// Create a new `Connection`, backed by `socket`. Read and write buffers 33 | /// are initialized. 34 | pub fn new(socket: TcpStream) -> Connection { 35 | Connection { 36 | stream: BufWriter::new(socket), 37 | // Default to a 4KB read buffer. For the use case of mini redis, 38 | // this is fine. However, real applications will want to tune this 39 | // value to their specific use case. There is a high likelihood that 40 | // a larger read buffer will work better. 41 | buffer: BytesMut::with_capacity(4 * 1024), 42 | } 43 | } 44 | 45 | /// Read a single `Frame` value from the underlying stream. 46 | /// 47 | /// The function waits until it has retrieved enough data to parse a frame. 48 | /// Any data remaining in the read buffer after the frame has been parsed is 49 | /// kept there for the next call to `read_frame`. 50 | /// 51 | /// # Returns 52 | /// 53 | /// On success, the received frame is returned. If the `TcpStream` 54 | /// is closed in a way that doesn't break a frame in half, it returns 55 | /// `None`. Otherwise, an error is returned. 56 | pub async fn read_frame(&mut self) -> crate::Result> { 57 | loop { 58 | // Attempt to parse a frame from the buffered data. If enough data 59 | // has been buffered, the frame is returned. 60 | if let Some(frame) = self.parse_frame()? { 61 | return Ok(Some(frame)); 62 | } 63 | 64 | // There is not enough buffered data to read a frame. Attempt to 65 | // read more data from the socket. 66 | // 67 | // On success, the number of bytes is returned. `0` indicates "end 68 | // of stream". 69 | if 0 == self.stream.read_buf(&mut self.buffer).await? { 70 | // The remote closed the connection. For this to be a clean 71 | // shutdown, there should be no data in the read buffer. If 72 | // there is, this means that the peer closed the socket while 73 | // sending a frame. 74 | if self.buffer.is_empty() { 75 | return Ok(None); 76 | } else { 77 | return Err("connection reset by peer".into()); 78 | } 79 | } 80 | } 81 | } 82 | 83 | /// Tries to parse a frame from the buffer. If the buffer contains enough 84 | /// data, the frame is returned and the data removed from the buffer. If not 85 | /// enough data has been buffered yet, `Ok(None)` is returned. If the 86 | /// buffered data does not represent a valid frame, `Err` is returned. 87 | fn parse_frame(&mut self) -> crate::Result> { 88 | use frame::Error::Incomplete; 89 | 90 | // Cursor is used to track the "current" location in the 91 | // buffer. Cursor also implements `Buf` from the `bytes` crate 92 | // which provides a number of helpful utilities for working 93 | // with bytes. 94 | let mut buf = Cursor::new(&self.buffer[..]); 95 | 96 | // The first step is to check if enough data has been buffered to parse 97 | // a single frame. This step is usually much faster than doing a full 98 | // parse of the frame, and allows us to skip allocating data structures 99 | // to hold the frame data unless we know the full frame has been 100 | // received. 101 | match Frame::check(&mut buf) { 102 | Ok(_) => { 103 | // The `check` function will have advanced the cursor until the 104 | // end of the frame. Since the cursor had position set to zero 105 | // before `Frame::check` was called, we obtain the length of the 106 | // frame by checking the cursor position. 107 | let len = buf.position() as usize; 108 | 109 | // Reset the position to zero before passing the cursor to 110 | // `Frame::parse`. 111 | buf.set_position(0); 112 | 113 | // Parse the frame from the buffer. This allocates the necessary 114 | // structures to represent the frame and returns the frame 115 | // value. 116 | // 117 | // If the encoded frame representation is invalid, an error is 118 | // returned. This should terminate the **current** connection 119 | // but should not impact any other connected client. 120 | let frame = Frame::parse(&mut buf)?; 121 | 122 | // Discard the parsed data from the read buffer. 123 | // 124 | // When `advance` is called on the read buffer, all of the data 125 | // up to `len` is discarded. The details of how this works is 126 | // left to `BytesMut`. This is often done by moving an internal 127 | // cursor, but it may be done by reallocating and copying data. 128 | self.buffer.advance(len); 129 | 130 | // Return the parsed frame to the caller. 131 | Ok(Some(frame)) 132 | } 133 | // There is not enough data present in the read buffer to parse a 134 | // single frame. We must wait for more data to be received from the 135 | // socket. Reading from the socket will be done in the statement 136 | // after this `match`. 137 | // 138 | // We do not want to return `Err` from here as this "error" is an 139 | // expected runtime condition. 140 | Err(Incomplete) => Ok(None), 141 | // An error was encountered while parsing the frame. The connection 142 | // is now in an invalid state. Returning `Err` from here will result 143 | // in the connection being closed. 144 | Err(e) => Err(e.into()), 145 | } 146 | } 147 | 148 | /// Write a single `Frame` value to the underlying stream. 149 | /// 150 | /// The `Frame` value is written to the socket using the various `write_*` 151 | /// functions provided by `AsyncWrite`. Calling these functions directly on 152 | /// a `TcpStream` is **not** advised, as this will result in a large number of 153 | /// syscalls. However, it is fine to call these functions on a *buffered* 154 | /// write stream. The data will be written to the buffer. Once the buffer is 155 | /// full, it is flushed to the underlying socket. 156 | pub async fn write_frame(&mut self, frame: &Frame) -> io::Result<()> { 157 | // Arrays are encoded by encoding each entry. All other frame types are 158 | // considered literals. For now, mini-redis is not able to encode 159 | // recursive frame structures. See below for more details. 160 | match frame { 161 | Frame::Array(val) => { 162 | // Encode the frame type prefix. For an array, it is `*`. 163 | self.stream.write_u8(b'*').await?; 164 | 165 | // Encode the length of the array. 166 | self.write_decimal(val.len() as u64).await?; 167 | 168 | // Iterate and encode each entry in the array. 169 | for entry in &**val { 170 | self.write_value(entry).await?; 171 | } 172 | } 173 | // The frame type is a literal. Encode the value directly. 174 | _ => self.write_value(frame).await?, 175 | } 176 | 177 | // Ensure the encoded frame is written to the socket. The calls above 178 | // are to the buffered stream and writes. Calling `flush` writes the 179 | // remaining contents of the buffer to the socket. 180 | self.stream.flush().await 181 | } 182 | 183 | /// Write a frame literal to the stream 184 | async fn write_value(&mut self, frame: &Frame) -> io::Result<()> { 185 | match frame { 186 | Frame::Simple(val) => { 187 | self.stream.write_u8(b'+').await?; 188 | self.stream.write_all(val.as_bytes()).await?; 189 | self.stream.write_all(b"\r\n").await?; 190 | } 191 | Frame::Error(val) => { 192 | self.stream.write_u8(b'-').await?; 193 | self.stream.write_all(val.as_bytes()).await?; 194 | self.stream.write_all(b"\r\n").await?; 195 | } 196 | Frame::Integer(val) => { 197 | self.stream.write_u8(b':').await?; 198 | self.write_decimal(*val).await?; 199 | } 200 | Frame::Null => { 201 | self.stream.write_all(b"$-1\r\n").await?; 202 | } 203 | Frame::Bulk(val) => { 204 | let len = val.len(); 205 | 206 | self.stream.write_u8(b'$').await?; 207 | self.write_decimal(len as u64).await?; 208 | self.stream.write_all(val).await?; 209 | self.stream.write_all(b"\r\n").await?; 210 | } 211 | // Encoding an `Array` from within a value cannot be done using a 212 | // recursive strategy. In general, async fns do not support 213 | // recursion. Mini-redis has not needed to encode nested arrays yet, 214 | // so for now it is skipped. 215 | Frame::Array(_val) => unreachable!(), 216 | } 217 | 218 | Ok(()) 219 | } 220 | 221 | /// Write a decimal frame to the stream 222 | async fn write_decimal(&mut self, val: u64) -> io::Result<()> { 223 | use std::io::Write; 224 | 225 | // Convert the value to a string 226 | let mut buf = [0u8; 20]; 227 | let mut buf = Cursor::new(&mut buf[..]); 228 | write!(&mut buf, "{}", val)?; 229 | 230 | let pos = buf.position() as usize; 231 | self.stream.write_all(&buf.get_ref()[..pos]).await?; 232 | self.stream.write_all(b"\r\n").await?; 233 | 234 | Ok(()) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::{broadcast, Notify}; 2 | use tokio::time::{self, Duration, Instant}; 3 | 4 | use bytes::Bytes; 5 | use std::collections::{BTreeSet, HashMap}; 6 | use std::sync::{Arc, Mutex}; 7 | use tracing::debug; 8 | 9 | /// A wrapper around a `Db` instance. This exists to allow orderly cleanup 10 | /// of the `Db` by signalling the background purge task to shut down when 11 | /// this struct is dropped. 12 | #[derive(Debug)] 13 | pub(crate) struct DbDropGuard { 14 | /// The `Db` instance that will be shut down when this `DbDropGuard` struct 15 | /// is dropped. 16 | db: Db, 17 | } 18 | 19 | /// Server state shared across all connections. 20 | /// 21 | /// `Db` contains a `HashMap` storing the key/value data and all 22 | /// `broadcast::Sender` values for active pub/sub channels. 23 | /// 24 | /// A `Db` instance is a handle to shared state. Cloning `Db` is shallow and 25 | /// only incurs an atomic ref count increment. 26 | /// 27 | /// When a `Db` value is created, a background task is spawned. This task is 28 | /// used to expire values after the requested duration has elapsed. The task 29 | /// runs until all instances of `Db` are dropped, at which point the task 30 | /// terminates. 31 | #[derive(Debug, Clone)] 32 | pub(crate) struct Db { 33 | /// Handle to shared state. The background task will also have an 34 | /// `Arc`. 35 | shared: Arc, 36 | } 37 | 38 | #[derive(Debug)] 39 | struct Shared { 40 | /// The shared state is guarded by a mutex. This is a `std::sync::Mutex` and 41 | /// not a Tokio mutex. This is because there are no asynchronous operations 42 | /// being performed while holding the mutex. Additionally, the critical 43 | /// sections are very small. 44 | /// 45 | /// A Tokio mutex is mostly intended to be used when locks need to be held 46 | /// across `.await` yield points. All other cases are **usually** best 47 | /// served by a std mutex. If the critical section does not include any 48 | /// async operations but is long (CPU intensive or performing blocking 49 | /// operations), then the entire operation, including waiting for the mutex, 50 | /// is considered a "blocking" operation and `tokio::task::spawn_blocking` 51 | /// should be used. 52 | state: Mutex, 53 | 54 | /// Notifies the background task handling entry expiration. The background 55 | /// task waits on this to be notified, then checks for expired values or the 56 | /// shutdown signal. 57 | background_task: Notify, 58 | } 59 | 60 | #[derive(Debug)] 61 | struct State { 62 | /// The key-value data. We are not trying to do anything fancy so a 63 | /// `std::collections::HashMap` works fine. 64 | entries: HashMap, 65 | 66 | /// The pub/sub key-space. Redis uses a **separate** key space for key-value 67 | /// and pub/sub. `mini-redis` handles this by using a separate `HashMap`. 68 | pub_sub: HashMap>, 69 | 70 | /// Tracks key TTLs. 71 | /// 72 | /// A `BTreeSet` is used to maintain expirations sorted by when they expire. 73 | /// This allows the background task to iterate this map to find the value 74 | /// expiring next. 75 | /// 76 | /// While highly unlikely, it is possible for more than one expiration to be 77 | /// created for the same instant. Because of this, the `Instant` is 78 | /// insufficient for the key. A unique key (`String`) is used to 79 | /// break these ties. 80 | expirations: BTreeSet<(Instant, String)>, 81 | 82 | /// True when the Db instance is shutting down. This happens when all `Db` 83 | /// values drop. Setting this to `true` signals to the background task to 84 | /// exit. 85 | shutdown: bool, 86 | } 87 | 88 | /// Entry in the key-value store 89 | #[derive(Debug)] 90 | struct Entry { 91 | /// Stored data 92 | data: Bytes, 93 | 94 | /// Instant at which the entry expires and should be removed from the 95 | /// database. 96 | expires_at: Option, 97 | } 98 | 99 | impl DbDropGuard { 100 | /// Create a new `DbDropGuard`, wrapping a `Db` instance. When this is dropped 101 | /// the `Db`'s purge task will be shut down. 102 | pub(crate) fn new() -> DbDropGuard { 103 | DbDropGuard { db: Db::new() } 104 | } 105 | 106 | /// Get the shared database. Internally, this is an 107 | /// `Arc`, so a clone only increments the ref count. 108 | pub(crate) fn db(&self) -> Db { 109 | self.db.clone() 110 | } 111 | } 112 | 113 | impl Drop for DbDropGuard { 114 | fn drop(&mut self) { 115 | // Signal the 'Db' instance to shut down the task that purges expired keys 116 | self.db.shutdown_purge_task(); 117 | } 118 | } 119 | 120 | impl Db { 121 | /// Create a new, empty, `Db` instance. Allocates shared state and spawns a 122 | /// background task to manage key expiration. 123 | pub(crate) fn new() -> Db { 124 | let shared = Arc::new(Shared { 125 | state: Mutex::new(State { 126 | entries: HashMap::new(), 127 | pub_sub: HashMap::new(), 128 | expirations: BTreeSet::new(), 129 | shutdown: false, 130 | }), 131 | background_task: Notify::new(), 132 | }); 133 | 134 | // Start the background task. 135 | tokio::spawn(purge_expired_tasks(shared.clone())); 136 | 137 | Db { shared } 138 | } 139 | 140 | /// Get the value associated with a key. 141 | /// 142 | /// Returns `None` if there is no value associated with the key. This may be 143 | /// due to never having assigned a value to the key or a previously assigned 144 | /// value expired. 145 | pub(crate) fn get(&self, key: &str) -> Option { 146 | // Acquire the lock, get the entry and clone the value. 147 | // 148 | // Because data is stored using `Bytes`, a clone here is a shallow 149 | // clone. Data is not copied. 150 | let state = self.shared.state.lock().unwrap(); 151 | state.entries.get(key).map(|entry| entry.data.clone()) 152 | } 153 | 154 | /// Set the value associated with a key along with an optional expiration 155 | /// Duration. 156 | /// 157 | /// If a value is already associated with the key, it is removed. 158 | pub(crate) fn set(&self, key: String, value: Bytes, expire: Option) { 159 | let mut state = self.shared.state.lock().unwrap(); 160 | 161 | // If this `set` becomes the key that expires **next**, the background 162 | // task needs to be notified so it can update its state. 163 | // 164 | // Whether or not the task needs to be notified is computed during the 165 | // `set` routine. 166 | let mut notify = false; 167 | 168 | let expires_at = expire.map(|duration| { 169 | // `Instant` at which the key expires. 170 | let when = Instant::now() + duration; 171 | 172 | // Only notify the worker task if the newly inserted expiration is the 173 | // **next** key to evict. In this case, the worker needs to be woken up 174 | // to update its state. 175 | notify = state 176 | .next_expiration() 177 | .map(|expiration| expiration > when) 178 | .unwrap_or(true); 179 | 180 | when 181 | }); 182 | 183 | // Insert the entry into the `HashMap`. 184 | let prev = state.entries.insert( 185 | key.clone(), 186 | Entry { 187 | data: value, 188 | expires_at, 189 | }, 190 | ); 191 | 192 | // If there was a value previously associated with the key **and** it 193 | // had an expiration time. The associated entry in the `expirations` map 194 | // must also be removed. This avoids leaking data. 195 | if let Some(prev) = prev { 196 | if let Some(when) = prev.expires_at { 197 | // clear expiration 198 | state.expirations.remove(&(when, key.clone())); 199 | } 200 | } 201 | 202 | // Track the expiration. If we insert before remove that will cause bug 203 | // when current `(when, key)` equals prev `(when, key)`. Remove then insert 204 | // can avoid this. 205 | if let Some(when) = expires_at { 206 | state.expirations.insert((when, key)); 207 | } 208 | 209 | // Release the mutex before notifying the background task. This helps 210 | // reduce contention by avoiding the background task waking up only to 211 | // be unable to acquire the mutex due to this function still holding it. 212 | drop(state); 213 | 214 | if notify { 215 | // Finally, only notify the background task if it needs to update 216 | // its state to reflect a new expiration. 217 | self.shared.background_task.notify_one(); 218 | } 219 | } 220 | 221 | /// Returns a `Receiver` for the requested channel. 222 | /// 223 | /// The returned `Receiver` is used to receive values broadcast by `PUBLISH` 224 | /// commands. 225 | pub(crate) fn subscribe(&self, key: String) -> broadcast::Receiver { 226 | use std::collections::hash_map::Entry; 227 | 228 | // Acquire the mutex 229 | let mut state = self.shared.state.lock().unwrap(); 230 | 231 | // If there is no entry for the requested channel, then create a new 232 | // broadcast channel and associate it with the key. If one already 233 | // exists, return an associated receiver. 234 | match state.pub_sub.entry(key) { 235 | Entry::Occupied(e) => e.get().subscribe(), 236 | Entry::Vacant(e) => { 237 | // No broadcast channel exists yet, so create one. 238 | // 239 | // The channel is created with a capacity of `1024` messages. A 240 | // message is stored in the channel until **all** subscribers 241 | // have seen it. This means that a slow subscriber could result 242 | // in messages being held indefinitely. 243 | // 244 | // When the channel's capacity fills up, publishing will result 245 | // in old messages being dropped. This prevents slow consumers 246 | // from blocking the entire system. 247 | let (tx, rx) = broadcast::channel(1024); 248 | e.insert(tx); 249 | rx 250 | } 251 | } 252 | } 253 | 254 | /// Publish a message to the channel. Returns the number of subscribers 255 | /// listening on the channel. 256 | pub(crate) fn publish(&self, key: &str, value: Bytes) -> usize { 257 | let state = self.shared.state.lock().unwrap(); 258 | 259 | state 260 | .pub_sub 261 | .get(key) 262 | // On a successful message send on the broadcast channel, the number 263 | // of subscribers is returned. An error indicates there are no 264 | // receivers, in which case, `0` should be returned. 265 | .map(|tx| tx.send(value).unwrap_or(0)) 266 | // If there is no entry for the channel key, then there are no 267 | // subscribers. In this case, return `0`. 268 | .unwrap_or(0) 269 | } 270 | 271 | /// Signals the purge background task to shut down. This is called by the 272 | /// `DbShutdown`s `Drop` implementation. 273 | fn shutdown_purge_task(&self) { 274 | // The background task must be signaled to shut down. This is done by 275 | // setting `State::shutdown` to `true` and signalling the task. 276 | let mut state = self.shared.state.lock().unwrap(); 277 | state.shutdown = true; 278 | 279 | // Drop the lock before signalling the background task. This helps 280 | // reduce lock contention by ensuring the background task doesn't 281 | // wake up only to be unable to acquire the mutex. 282 | drop(state); 283 | self.shared.background_task.notify_one(); 284 | } 285 | } 286 | 287 | impl Shared { 288 | /// Purge all expired keys and return the `Instant` at which the **next** 289 | /// key will expire. The background task will sleep until this instant. 290 | fn purge_expired_keys(&self) -> Option { 291 | let mut state = self.state.lock().unwrap(); 292 | 293 | if state.shutdown { 294 | // The database is shutting down. All handles to the shared state 295 | // have dropped. The background task should exit. 296 | return None; 297 | } 298 | 299 | // This is needed to make the borrow checker happy. In short, `lock()` 300 | // returns a `MutexGuard` and not a `&mut State`. The borrow checker is 301 | // not able to see "through" the mutex guard and determine that it is 302 | // safe to access both `state.expirations` and `state.entries` mutably, 303 | // so we get a "real" mutable reference to `State` outside of the loop. 304 | let state = &mut *state; 305 | 306 | // Find all keys scheduled to expire **before** now. 307 | let now = Instant::now(); 308 | 309 | while let Some(&(when, ref key)) = state.expirations.iter().next() { 310 | if when > now { 311 | // Done purging, `when` is the instant at which the next key 312 | // expires. The worker task will wait until this instant. 313 | return Some(when); 314 | } 315 | 316 | // The key expired, remove it 317 | state.entries.remove(key); 318 | state.expirations.remove(&(when, key.clone())); 319 | } 320 | 321 | None 322 | } 323 | 324 | /// Returns `true` if the database is shutting down 325 | /// 326 | /// The `shutdown` flag is set when all `Db` values have dropped, indicating 327 | /// that the shared state can no longer be accessed. 328 | fn is_shutdown(&self) -> bool { 329 | self.state.lock().unwrap().shutdown 330 | } 331 | } 332 | 333 | impl State { 334 | fn next_expiration(&self) -> Option { 335 | self.expirations 336 | .iter() 337 | .next() 338 | .map(|expiration| expiration.0) 339 | } 340 | } 341 | 342 | /// Routine executed by the background task. 343 | /// 344 | /// Wait to be notified. On notification, purge any expired keys from the shared 345 | /// state handle. If `shutdown` is set, terminate the task. 346 | async fn purge_expired_tasks(shared: Arc) { 347 | // If the shutdown flag is set, then the task should exit. 348 | while !shared.is_shutdown() { 349 | // Purge all keys that are expired. The function returns the instant at 350 | // which the **next** key will expire. The worker should wait until the 351 | // instant has passed then purge again. 352 | if let Some(when) = shared.purge_expired_keys() { 353 | // Wait until the next key expires **or** until the background task 354 | // is notified. If the task is notified, then it must reload its 355 | // state as new keys have been set to expire early. This is done by 356 | // looping. 357 | tokio::select! { 358 | _ = time::sleep_until(when) => {} 359 | _ = shared.background_task.notified() => {} 360 | } 361 | } else { 362 | // There are no keys expiring in the future. Wait until the task is 363 | // notified. 364 | shared.background_task.notified().await; 365 | } 366 | } 367 | 368 | debug!("Purge background task shut down") 369 | } 370 | -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | //! Provides a type representing a Redis protocol frame as well as utilities for 2 | //! parsing frames from a byte array. 3 | 4 | use bytes::{Buf, Bytes}; 5 | use std::convert::TryInto; 6 | use std::fmt; 7 | use std::io::Cursor; 8 | use std::num::TryFromIntError; 9 | use std::string::FromUtf8Error; 10 | 11 | /// A frame in the Redis protocol. 12 | #[derive(Clone, Debug)] 13 | pub enum Frame { 14 | Simple(String), 15 | Error(String), 16 | Integer(u64), 17 | Bulk(Bytes), 18 | Null, 19 | Array(Vec), 20 | } 21 | 22 | #[derive(Debug)] 23 | pub enum Error { 24 | /// Not enough data is available to parse a message 25 | Incomplete, 26 | 27 | /// Invalid message encoding 28 | Other(crate::Error), 29 | } 30 | 31 | impl Frame { 32 | /// Returns an empty array 33 | pub(crate) fn array() -> Frame { 34 | Frame::Array(vec![]) 35 | } 36 | 37 | /// Push a "bulk" frame into the array. `self` must be an Array frame. 38 | /// 39 | /// # Panics 40 | /// 41 | /// panics if `self` is not an array 42 | pub(crate) fn push_bulk(&mut self, bytes: Bytes) { 43 | match self { 44 | Frame::Array(vec) => { 45 | vec.push(Frame::Bulk(bytes)); 46 | } 47 | _ => panic!("not an array frame"), 48 | } 49 | } 50 | 51 | /// Push an "integer" frame into the array. `self` must be an Array frame. 52 | /// 53 | /// # Panics 54 | /// 55 | /// panics if `self` is not an array 56 | pub(crate) fn push_int(&mut self, value: u64) { 57 | match self { 58 | Frame::Array(vec) => { 59 | vec.push(Frame::Integer(value)); 60 | } 61 | _ => panic!("not an array frame"), 62 | } 63 | } 64 | 65 | /// Checks if an entire message can be decoded from `src` 66 | pub fn check(src: &mut Cursor<&[u8]>) -> Result<(), Error> { 67 | match get_u8(src)? { 68 | b'+' => { 69 | get_line(src)?; 70 | Ok(()) 71 | } 72 | b'-' => { 73 | get_line(src)?; 74 | Ok(()) 75 | } 76 | b':' => { 77 | let _ = get_decimal(src)?; 78 | Ok(()) 79 | } 80 | b'$' => { 81 | if b'-' == peek_u8(src)? { 82 | // Skip '-1\r\n' 83 | skip(src, 4) 84 | } else { 85 | // Read the bulk string 86 | let len: usize = get_decimal(src)?.try_into()?; 87 | 88 | // skip that number of bytes + 2 (\r\n). 89 | skip(src, len + 2) 90 | } 91 | } 92 | b'*' => { 93 | let len = get_decimal(src)?; 94 | 95 | for _ in 0..len { 96 | Frame::check(src)?; 97 | } 98 | 99 | Ok(()) 100 | } 101 | actual => Err(format!("protocol error; invalid frame type byte `{}`", actual).into()), 102 | } 103 | } 104 | 105 | /// The message has already been validated with `check`. 106 | pub fn parse(src: &mut Cursor<&[u8]>) -> Result { 107 | match get_u8(src)? { 108 | b'+' => { 109 | // Read the line and convert it to `Vec` 110 | let line = get_line(src)?.to_vec(); 111 | 112 | // Convert the line to a String 113 | let string = String::from_utf8(line)?; 114 | 115 | Ok(Frame::Simple(string)) 116 | } 117 | b'-' => { 118 | // Read the line and convert it to `Vec` 119 | let line = get_line(src)?.to_vec(); 120 | 121 | // Convert the line to a String 122 | let string = String::from_utf8(line)?; 123 | 124 | Ok(Frame::Error(string)) 125 | } 126 | b':' => { 127 | let len = get_decimal(src)?; 128 | Ok(Frame::Integer(len)) 129 | } 130 | b'$' => { 131 | if b'-' == peek_u8(src)? { 132 | let line = get_line(src)?; 133 | 134 | if line != b"-1" { 135 | return Err("protocol error; invalid frame format".into()); 136 | } 137 | 138 | Ok(Frame::Null) 139 | } else { 140 | // Read the bulk string 141 | let len = get_decimal(src)?.try_into()?; 142 | let n = len + 2; 143 | 144 | if src.remaining() < n { 145 | return Err(Error::Incomplete); 146 | } 147 | 148 | let data = Bytes::copy_from_slice(&src.chunk()[..len]); 149 | 150 | // skip that number of bytes + 2 (\r\n). 151 | skip(src, n)?; 152 | 153 | Ok(Frame::Bulk(data)) 154 | } 155 | } 156 | b'*' => { 157 | let len = get_decimal(src)?.try_into()?; 158 | let mut out = Vec::with_capacity(len); 159 | 160 | for _ in 0..len { 161 | out.push(Frame::parse(src)?); 162 | } 163 | 164 | Ok(Frame::Array(out)) 165 | } 166 | _ => unimplemented!(), 167 | } 168 | } 169 | 170 | /// Converts the frame to an "unexpected frame" error 171 | pub(crate) fn to_error(&self) -> crate::Error { 172 | format!("unexpected frame: {}", self).into() 173 | } 174 | } 175 | 176 | impl PartialEq<&str> for Frame { 177 | fn eq(&self, other: &&str) -> bool { 178 | match self { 179 | Frame::Simple(s) => s.eq(other), 180 | Frame::Bulk(s) => s.eq(other), 181 | _ => false, 182 | } 183 | } 184 | } 185 | 186 | impl fmt::Display for Frame { 187 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 188 | use std::str; 189 | 190 | match self { 191 | Frame::Simple(response) => response.fmt(fmt), 192 | Frame::Error(msg) => write!(fmt, "error: {}", msg), 193 | Frame::Integer(num) => num.fmt(fmt), 194 | Frame::Bulk(msg) => match str::from_utf8(msg) { 195 | Ok(string) => string.fmt(fmt), 196 | Err(_) => write!(fmt, "{:?}", msg), 197 | }, 198 | Frame::Null => "(nil)".fmt(fmt), 199 | Frame::Array(parts) => { 200 | for (i, part) in parts.iter().enumerate() { 201 | if i > 0 { 202 | // use space as the array element display separator 203 | write!(fmt, " ")?; 204 | } 205 | 206 | part.fmt(fmt)?; 207 | } 208 | 209 | Ok(()) 210 | } 211 | } 212 | } 213 | } 214 | 215 | fn peek_u8(src: &mut Cursor<&[u8]>) -> Result { 216 | if !src.has_remaining() { 217 | return Err(Error::Incomplete); 218 | } 219 | 220 | Ok(src.chunk()[0]) 221 | } 222 | 223 | fn get_u8(src: &mut Cursor<&[u8]>) -> Result { 224 | if !src.has_remaining() { 225 | return Err(Error::Incomplete); 226 | } 227 | 228 | Ok(src.get_u8()) 229 | } 230 | 231 | fn skip(src: &mut Cursor<&[u8]>, n: usize) -> Result<(), Error> { 232 | if src.remaining() < n { 233 | return Err(Error::Incomplete); 234 | } 235 | 236 | src.advance(n); 237 | Ok(()) 238 | } 239 | 240 | /// Read a new-line terminated decimal 241 | fn get_decimal(src: &mut Cursor<&[u8]>) -> Result { 242 | use atoi::atoi; 243 | 244 | let line = get_line(src)?; 245 | 246 | atoi::(line).ok_or_else(|| "protocol error; invalid frame format".into()) 247 | } 248 | 249 | /// Find a line 250 | fn get_line<'a>(src: &mut Cursor<&'a [u8]>) -> Result<&'a [u8], Error> { 251 | // Scan the bytes directly 252 | let start = src.position() as usize; 253 | // Scan to the second to last byte 254 | let end = src.get_ref().len() - 1; 255 | 256 | for i in start..end { 257 | if src.get_ref()[i] == b'\r' && src.get_ref()[i + 1] == b'\n' { 258 | // We found a line, update the position to be *after* the \n 259 | src.set_position((i + 2) as u64); 260 | 261 | // Return the line 262 | return Ok(&src.get_ref()[start..i]); 263 | } 264 | } 265 | 266 | Err(Error::Incomplete) 267 | } 268 | 269 | impl From for Error { 270 | fn from(src: String) -> Error { 271 | Error::Other(src.into()) 272 | } 273 | } 274 | 275 | impl From<&str> for Error { 276 | fn from(src: &str) -> Error { 277 | src.to_string().into() 278 | } 279 | } 280 | 281 | impl From for Error { 282 | fn from(_src: FromUtf8Error) -> Error { 283 | "protocol error; invalid frame format".into() 284 | } 285 | } 286 | 287 | impl From for Error { 288 | fn from(_src: TryFromIntError) -> Error { 289 | "protocol error; invalid frame format".into() 290 | } 291 | } 292 | 293 | impl std::error::Error for Error {} 294 | 295 | impl fmt::Display for Error { 296 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 297 | match self { 298 | Error::Incomplete => "stream ended early".fmt(fmt), 299 | Error::Other(err) => err.fmt(fmt), 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A minimal (i.e. very incomplete) implementation of a Redis server and 2 | //! client. 3 | //! 4 | //! The purpose of this project is to provide a larger example of an 5 | //! asynchronous Rust project built with Tokio. Do not attempt to run this in 6 | //! production... seriously. 7 | //! 8 | //! # Layout 9 | //! 10 | //! The library is structured such that it can be used with guides. There are 11 | //! modules that are public that probably would not be public in a "real" redis 12 | //! client library. 13 | //! 14 | //! The major components are: 15 | //! 16 | //! * `server`: Redis server implementation. Includes a single `run` function 17 | //! that takes a `TcpListener` and starts accepting redis client connections. 18 | //! 19 | //! * `clients/client`: an asynchronous Redis client implementation. Demonstrates how to 20 | //! build clients with Tokio. 21 | //! 22 | //! * `cmd`: implementations of the supported Redis commands. 23 | //! 24 | //! * `frame`: represents a single Redis protocol frame. A frame is used as an 25 | //! intermediate representation between a "command" and the byte 26 | //! representation. 27 | 28 | pub mod clients; 29 | pub use clients::{BlockingClient, BufferedClient, Client}; 30 | 31 | pub mod cmd; 32 | pub use cmd::Command; 33 | 34 | mod connection; 35 | pub use connection::Connection; 36 | 37 | pub mod frame; 38 | pub use frame::Frame; 39 | 40 | mod db; 41 | use db::Db; 42 | use db::DbDropGuard; 43 | 44 | mod parse; 45 | use parse::{Parse, ParseError}; 46 | 47 | pub mod server; 48 | 49 | mod shutdown; 50 | use shutdown::Shutdown; 51 | 52 | /// Default port that a redis server listens on. 53 | /// 54 | /// Used if no port is specified. 55 | pub const DEFAULT_PORT: u16 = 6379; 56 | 57 | /// Error returned by most functions. 58 | /// 59 | /// When writing a real application, one might want to consider a specialized 60 | /// error handling crate or defining an error type as an `enum` of causes. 61 | /// However, for our example, using a boxed `std::error::Error` is sufficient. 62 | /// 63 | /// For performance reasons, boxing is avoided in any hot path. For example, in 64 | /// `parse`, a custom error `enum` is defined. This is because the error is hit 65 | /// and handled during normal execution when a partial frame is received on a 66 | /// socket. `std::error::Error` is implemented for `parse::Error` which allows 67 | /// it to be converted to `Box`. 68 | pub type Error = Box; 69 | 70 | /// A specialized `Result` type for mini-redis operations. 71 | /// 72 | /// This is defined as a convenience. 73 | pub type Result = std::result::Result; 74 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::Frame; 2 | 3 | use bytes::Bytes; 4 | use std::{fmt, str, vec}; 5 | 6 | /// Utility for parsing a command 7 | /// 8 | /// Commands are represented as array frames. Each entry in the frame is a 9 | /// "token". A `Parse` is initialized with the array frame and provides a 10 | /// cursor-like API. Each command struct includes a `parse_frame` method that 11 | /// uses a `Parse` to extract its fields. 12 | #[derive(Debug)] 13 | pub(crate) struct Parse { 14 | /// Array frame iterator. 15 | parts: vec::IntoIter, 16 | } 17 | 18 | /// Error encountered while parsing a frame. 19 | /// 20 | /// Only `EndOfStream` errors are handled at runtime. All other errors result in 21 | /// the connection being terminated. 22 | #[derive(Debug)] 23 | pub(crate) enum ParseError { 24 | /// Attempting to extract a value failed due to the frame being fully 25 | /// consumed. 26 | EndOfStream, 27 | 28 | /// All other errors 29 | Other(crate::Error), 30 | } 31 | 32 | impl Parse { 33 | /// Create a new `Parse` to parse the contents of `frame`. 34 | /// 35 | /// Returns `Err` if `frame` is not an array frame. 36 | pub(crate) fn new(frame: Frame) -> Result { 37 | let array = match frame { 38 | Frame::Array(array) => array, 39 | frame => return Err(format!("protocol error; expected array, got {:?}", frame).into()), 40 | }; 41 | 42 | Ok(Parse { 43 | parts: array.into_iter(), 44 | }) 45 | } 46 | 47 | /// Return the next entry. Array frames are arrays of frames, so the next 48 | /// entry is a frame. 49 | fn next(&mut self) -> Result { 50 | self.parts.next().ok_or(ParseError::EndOfStream) 51 | } 52 | 53 | /// Return the next entry as a string. 54 | /// 55 | /// If the next entry cannot be represented as a String, then an error is returned. 56 | pub(crate) fn next_string(&mut self) -> Result { 57 | match self.next()? { 58 | // Both `Simple` and `Bulk` representation may be strings. Strings 59 | // are parsed to UTF-8. 60 | // 61 | // While errors are stored as strings, they are considered separate 62 | // types. 63 | Frame::Simple(s) => Ok(s), 64 | Frame::Bulk(data) => str::from_utf8(&data[..]) 65 | .map(|s| s.to_string()) 66 | .map_err(|_| "protocol error; invalid string".into()), 67 | frame => Err(format!( 68 | "protocol error; expected simple frame or bulk frame, got {:?}", 69 | frame 70 | ) 71 | .into()), 72 | } 73 | } 74 | 75 | /// Return the next entry as raw bytes. 76 | /// 77 | /// If the next entry cannot be represented as raw bytes, an error is 78 | /// returned. 79 | pub(crate) fn next_bytes(&mut self) -> Result { 80 | match self.next()? { 81 | // Both `Simple` and `Bulk` representation may be raw bytes. 82 | // 83 | // Although errors are stored as strings and could be represented as 84 | // raw bytes, they are considered separate types. 85 | Frame::Simple(s) => Ok(Bytes::from(s.into_bytes())), 86 | Frame::Bulk(data) => Ok(data), 87 | frame => Err(format!( 88 | "protocol error; expected simple frame or bulk frame, got {:?}", 89 | frame 90 | ) 91 | .into()), 92 | } 93 | } 94 | 95 | /// Return the next entry as an integer. 96 | /// 97 | /// This includes `Simple`, `Bulk`, and `Integer` frame types. `Simple` and 98 | /// `Bulk` frame types are parsed. 99 | /// 100 | /// If the next entry cannot be represented as an integer, then an error is 101 | /// returned. 102 | pub(crate) fn next_int(&mut self) -> Result { 103 | use atoi::atoi; 104 | 105 | const MSG: &str = "protocol error; invalid number"; 106 | 107 | match self.next()? { 108 | // An integer frame type is already stored as an integer. 109 | Frame::Integer(v) => Ok(v), 110 | // Simple and bulk frames must be parsed as integers. If the parsing 111 | // fails, an error is returned. 112 | Frame::Simple(data) => atoi::(data.as_bytes()).ok_or_else(|| MSG.into()), 113 | Frame::Bulk(data) => atoi::(&data).ok_or_else(|| MSG.into()), 114 | frame => Err(format!("protocol error; expected int frame but got {:?}", frame).into()), 115 | } 116 | } 117 | 118 | /// Ensure there are no more entries in the array 119 | pub(crate) fn finish(&mut self) -> Result<(), ParseError> { 120 | if self.parts.next().is_none() { 121 | Ok(()) 122 | } else { 123 | Err("protocol error; expected end of frame, but there was more".into()) 124 | } 125 | } 126 | } 127 | 128 | impl From for ParseError { 129 | fn from(src: String) -> ParseError { 130 | ParseError::Other(src.into()) 131 | } 132 | } 133 | 134 | impl From<&str> for ParseError { 135 | fn from(src: &str) -> ParseError { 136 | src.to_string().into() 137 | } 138 | } 139 | 140 | impl fmt::Display for ParseError { 141 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 142 | match self { 143 | ParseError::EndOfStream => "protocol error; unexpected end of stream".fmt(f), 144 | ParseError::Other(err) => err.fmt(f), 145 | } 146 | } 147 | } 148 | 149 | impl std::error::Error for ParseError {} 150 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! Minimal Redis server implementation 2 | //! 3 | //! Provides an async `run` function that listens for inbound connections, 4 | //! spawning a task per connection. 5 | 6 | use crate::{Command, Connection, Db, DbDropGuard, Shutdown}; 7 | 8 | use std::future::Future; 9 | use std::sync::Arc; 10 | use tokio::net::{TcpListener, TcpStream}; 11 | use tokio::sync::{broadcast, mpsc, Semaphore}; 12 | use tokio::time::{self, Duration}; 13 | use tracing::{debug, error, info, instrument}; 14 | 15 | /// Server listener state. Created in the `run` call. It includes a `run` method 16 | /// which performs the TCP listening and initialization of per-connection state. 17 | #[derive(Debug)] 18 | struct Listener { 19 | /// Shared database handle. 20 | /// 21 | /// Contains the key / value store as well as the broadcast channels for 22 | /// pub/sub. 23 | /// 24 | /// This holds a wrapper around an `Arc`. The internal `Db` can be 25 | /// retrieved and passed into the per connection state (`Handler`). 26 | db_holder: DbDropGuard, 27 | 28 | /// TCP listener supplied by the `run` caller. 29 | listener: TcpListener, 30 | 31 | /// Limit the max number of connections. 32 | /// 33 | /// A `Semaphore` is used to limit the max number of connections. Before 34 | /// attempting to accept a new connection, a permit is acquired from the 35 | /// semaphore. If none are available, the listener waits for one. 36 | /// 37 | /// When handlers complete processing a connection, the permit is returned 38 | /// to the semaphore. 39 | limit_connections: Arc, 40 | 41 | /// Broadcasts a shutdown signal to all active connections. 42 | /// 43 | /// The initial `shutdown` trigger is provided by the `run` caller. The 44 | /// server is responsible for gracefully shutting down active connections. 45 | /// When a connection task is spawned, it is passed a broadcast receiver 46 | /// handle. When a graceful shutdown is initiated, a `()` value is sent via 47 | /// the broadcast::Sender. Each active connection receives it, reaches a 48 | /// safe terminal state, and completes the task. 49 | notify_shutdown: broadcast::Sender<()>, 50 | 51 | /// Used as part of the graceful shutdown process to wait for client 52 | /// connections to complete processing. 53 | /// 54 | /// Tokio channels are closed once all `Sender` handles go out of scope. 55 | /// When a channel is closed, the receiver receives `None`. This is 56 | /// leveraged to detect all connection handlers completing. When a 57 | /// connection handler is initialized, it is assigned a clone of 58 | /// `shutdown_complete_tx`. When the listener shuts down, it drops the 59 | /// sender held by this `shutdown_complete_tx` field. Once all handler tasks 60 | /// complete, all clones of the `Sender` are also dropped. This results in 61 | /// `shutdown_complete_rx.recv()` completing with `None`. At this point, it 62 | /// is safe to exit the server process. 63 | shutdown_complete_tx: mpsc::Sender<()>, 64 | } 65 | 66 | /// Per-connection handler. Reads requests from `connection` and applies the 67 | /// commands to `db`. 68 | #[derive(Debug)] 69 | struct Handler { 70 | /// Shared database handle. 71 | /// 72 | /// When a command is received from `connection`, it is applied with `db`. 73 | /// The implementation of the command is in the `cmd` module. Each command 74 | /// will need to interact with `db` in order to complete the work. 75 | db: Db, 76 | 77 | /// The TCP connection decorated with the redis protocol encoder / decoder 78 | /// implemented using a buffered `TcpStream`. 79 | /// 80 | /// When `Listener` receives an inbound connection, the `TcpStream` is 81 | /// passed to `Connection::new`, which initializes the associated buffers. 82 | /// `Connection` allows the handler to operate at the "frame" level and keep 83 | /// the byte level protocol parsing details encapsulated in `Connection`. 84 | connection: Connection, 85 | 86 | /// Listen for shutdown notifications. 87 | /// 88 | /// A wrapper around the `broadcast::Receiver` paired with the sender in 89 | /// `Listener`. The connection handler processes requests from the 90 | /// connection until the peer disconnects **or** a shutdown notification is 91 | /// received from `shutdown`. In the latter case, any in-flight work being 92 | /// processed for the peer is continued until it reaches a safe state, at 93 | /// which point the connection is terminated. 94 | shutdown: Shutdown, 95 | 96 | /// Not used directly. Instead, when `Handler` is dropped...? 97 | _shutdown_complete: mpsc::Sender<()>, 98 | } 99 | 100 | /// Maximum number of concurrent connections the redis server will accept. 101 | /// 102 | /// When this limit is reached, the server will stop accepting connections until 103 | /// an active connection terminates. 104 | /// 105 | /// A real application will want to make this value configurable, but for this 106 | /// example, it is hard coded. 107 | /// 108 | /// This is also set to a pretty low value to discourage using this in 109 | /// production (you'd think that all the disclaimers would make it obvious that 110 | /// this is not a serious project... but I thought that about mini-http as 111 | /// well). 112 | const MAX_CONNECTIONS: usize = 250; 113 | 114 | /// Run the mini-redis server. 115 | /// 116 | /// Accepts connections from the supplied listener. For each inbound connection, 117 | /// a task is spawned to handle that connection. The server runs until the 118 | /// `shutdown` future completes, at which point the server shuts down 119 | /// gracefully. 120 | /// 121 | /// `tokio::signal::ctrl_c()` can be used as the `shutdown` argument. This will 122 | /// listen for a SIGINT signal. 123 | pub async fn run(listener: TcpListener, shutdown: impl Future) { 124 | // When the provided `shutdown` future completes, we must send a shutdown 125 | // message to all active connections. We use a broadcast channel for this 126 | // purpose. The call below ignores the receiver of the broadcast pair, and when 127 | // a receiver is needed, the subscribe() method on the sender is used to create 128 | // one. 129 | let (notify_shutdown, _) = broadcast::channel(1); 130 | let (shutdown_complete_tx, mut shutdown_complete_rx) = mpsc::channel(1); 131 | 132 | // Initialize the listener state 133 | let mut server = Listener { 134 | listener, 135 | db_holder: DbDropGuard::new(), 136 | limit_connections: Arc::new(Semaphore::new(MAX_CONNECTIONS)), 137 | notify_shutdown, 138 | shutdown_complete_tx, 139 | }; 140 | 141 | // Concurrently run the server and listen for the `shutdown` signal. The 142 | // server task runs until an error is encountered, so under normal 143 | // circumstances, this `select!` statement runs until the `shutdown` signal 144 | // is received. 145 | // 146 | // `select!` statements are written in the form of: 147 | // 148 | // ``` 149 | // = => 150 | // ``` 151 | // 152 | // All `` statements are executed concurrently. Once the **first** 153 | // op completes, its associated `` is 154 | // performed. 155 | // 156 | // The `select!` macro is a foundational building block for writing 157 | // asynchronous Rust. See the API docs for more details: 158 | // 159 | // https://docs.rs/tokio/*/tokio/macro.select.html 160 | tokio::select! { 161 | res = server.run() => { 162 | // If an error is received here, accepting connections from the TCP 163 | // listener failed multiple times and the server is giving up and 164 | // shutting down. 165 | // 166 | // Errors encountered when handling individual connections do not 167 | // bubble up to this point. 168 | if let Err(err) = res { 169 | error!(cause = %err, "failed to accept"); 170 | } 171 | } 172 | _ = shutdown => { 173 | // The shutdown signal has been received. 174 | info!("shutting down"); 175 | } 176 | } 177 | 178 | // Extract the `shutdown_complete` receiver and transmitter 179 | // explicitly drop `shutdown_transmitter`. This is important, as the 180 | // `.await` below would otherwise never complete. 181 | let Listener { 182 | shutdown_complete_tx, 183 | notify_shutdown, 184 | .. 185 | } = server; 186 | 187 | // When `notify_shutdown` is dropped, all tasks which have `subscribe`d will 188 | // receive the shutdown signal and can exit 189 | drop(notify_shutdown); 190 | // Drop final `Sender` so the `Receiver` below can complete 191 | drop(shutdown_complete_tx); 192 | 193 | // Wait for all active connections to finish processing. As the `Sender` 194 | // handle held by the listener has been dropped above, the only remaining 195 | // `Sender` instances are held by connection handler tasks. When those drop, 196 | // the `mpsc` channel will close and `recv()` will return `None`. 197 | let _ = shutdown_complete_rx.recv().await; 198 | } 199 | 200 | impl Listener { 201 | /// Run the server 202 | /// 203 | /// Listen for inbound connections. For each inbound connection, spawn a 204 | /// task to process that connection. 205 | /// 206 | /// # Errors 207 | /// 208 | /// Returns `Err` if accepting returns an error. This can happen for a 209 | /// number reasons that resolve over time. For example, if the underlying 210 | /// operating system has reached an internal limit for max number of 211 | /// sockets, accept will fail. 212 | /// 213 | /// The process is not able to detect when a transient error resolves 214 | /// itself. One strategy for handling this is to implement a back off 215 | /// strategy, which is what we do here. 216 | async fn run(&mut self) -> crate::Result<()> { 217 | info!("accepting inbound connections"); 218 | 219 | loop { 220 | // Wait for a permit to become available 221 | // 222 | // `acquire_owned` returns a permit that is bound to the semaphore. 223 | // When the permit value is dropped, it is automatically returned 224 | // to the semaphore. 225 | // 226 | // `acquire_owned()` returns `Err` when the semaphore has been 227 | // closed. We don't ever close the semaphore, so `unwrap()` is safe. 228 | let permit = self 229 | .limit_connections 230 | .clone() 231 | .acquire_owned() 232 | .await 233 | .unwrap(); 234 | 235 | // Accept a new socket. This will attempt to perform error handling. 236 | // The `accept` method internally attempts to recover errors, so an 237 | // error here is non-recoverable. 238 | let socket = self.accept().await?; 239 | 240 | // Create the necessary per-connection handler state. 241 | let mut handler = Handler { 242 | // Get a handle to the shared database. 243 | db: self.db_holder.db(), 244 | 245 | // Initialize the connection state. This allocates read/write 246 | // buffers to perform redis protocol frame parsing. 247 | connection: Connection::new(socket), 248 | 249 | // Receive shutdown notifications. 250 | shutdown: Shutdown::new(self.notify_shutdown.subscribe()), 251 | 252 | // Notifies the receiver half once all clones are 253 | // dropped. 254 | _shutdown_complete: self.shutdown_complete_tx.clone(), 255 | }; 256 | 257 | // Spawn a new task to process the connections. Tokio tasks are like 258 | // asynchronous green threads and are executed concurrently. 259 | tokio::spawn(async move { 260 | // Process the connection. If an error is encountered, log it. 261 | if let Err(err) = handler.run().await { 262 | error!(cause = ?err, "connection error"); 263 | } 264 | // Move the permit into the task and drop it after completion. 265 | // This returns the permit back to the semaphore. 266 | drop(permit); 267 | }); 268 | } 269 | } 270 | 271 | /// Accept an inbound connection. 272 | /// 273 | /// Errors are handled by backing off and retrying. An exponential backoff 274 | /// strategy is used. After the first failure, the task waits for 1 second. 275 | /// After the second failure, the task waits for 2 seconds. Each subsequent 276 | /// failure doubles the wait time. If accepting fails on the 6th try after 277 | /// waiting for 64 seconds, then this function returns with an error. 278 | async fn accept(&mut self) -> crate::Result { 279 | let mut backoff = 1; 280 | 281 | // Try to accept a few times 282 | loop { 283 | // Perform the accept operation. If a socket is successfully 284 | // accepted, return it. Otherwise, save the error. 285 | match self.listener.accept().await { 286 | Ok((socket, _)) => return Ok(socket), 287 | Err(err) => { 288 | if backoff > 64 { 289 | // Accept has failed too many times. Return the error. 290 | return Err(err.into()); 291 | } 292 | } 293 | } 294 | 295 | // Pause execution until the back off period elapses. 296 | time::sleep(Duration::from_secs(backoff)).await; 297 | 298 | // Double the back off 299 | backoff *= 2; 300 | } 301 | } 302 | } 303 | 304 | impl Handler { 305 | /// Process a single connection. 306 | /// 307 | /// Request frames are read from the socket and processed. Responses are 308 | /// written back to the socket. 309 | /// 310 | /// Currently, pipelining is not implemented. Pipelining is the ability to 311 | /// process more than one request concurrently per connection without 312 | /// interleaving frames. See for more details: 313 | /// https://redis.io/topics/pipelining 314 | /// 315 | /// When the shutdown signal is received, the connection is processed until 316 | /// it reaches a safe state, at which point it is terminated. 317 | #[instrument(skip(self))] 318 | async fn run(&mut self) -> crate::Result<()> { 319 | // As long as the shutdown signal has not been received, try to read a 320 | // new request frame. 321 | while !self.shutdown.is_shutdown() { 322 | // While reading a request frame, also listen for the shutdown 323 | // signal. 324 | let maybe_frame = tokio::select! { 325 | res = self.connection.read_frame() => res?, 326 | _ = self.shutdown.recv() => { 327 | // If a shutdown signal is received, return from `run`. 328 | // This will result in the task terminating. 329 | return Ok(()); 330 | } 331 | }; 332 | 333 | // If `None` is returned from `read_frame()` then the peer closed 334 | // the socket. There is no further work to do and the task can be 335 | // terminated. 336 | let frame = match maybe_frame { 337 | Some(frame) => frame, 338 | None => return Ok(()), 339 | }; 340 | 341 | // Convert the redis frame into a command struct. This returns an 342 | // error if the frame is not a valid redis command or it is an 343 | // unsupported command. 344 | let cmd = Command::from_frame(frame)?; 345 | 346 | // Logs the `cmd` object. The syntax here is a shorthand provided by 347 | // the `tracing` crate. It can be thought of as similar to: 348 | // 349 | // ``` 350 | // debug!(cmd = format!("{:?}", cmd)); 351 | // ``` 352 | // 353 | // `tracing` provides structured logging, so information is "logged" 354 | // as key-value pairs. 355 | debug!(?cmd); 356 | 357 | // Perform the work needed to apply the command. This may mutate the 358 | // database state as a result. 359 | // 360 | // The connection is passed into the apply function which allows the 361 | // command to write response frames directly to the connection. In 362 | // the case of pub/sub, multiple frames may be send back to the 363 | // peer. 364 | cmd.apply(&self.db, &mut self.connection, &mut self.shutdown) 365 | .await?; 366 | } 367 | 368 | Ok(()) 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/shutdown.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::broadcast; 2 | 3 | /// Listens for the server shutdown signal. 4 | /// 5 | /// Shutdown is signalled using a `broadcast::Receiver`. Only a single value is 6 | /// ever sent. Once a value has been sent via the broadcast channel, the server 7 | /// should shutdown. 8 | /// 9 | /// The `Shutdown` struct listens for the signal and tracks that the signal has 10 | /// been received. Callers may query for whether the shutdown signal has been 11 | /// received or not. 12 | #[derive(Debug)] 13 | pub(crate) struct Shutdown { 14 | /// `true` if the shutdown signal has been received 15 | is_shutdown: bool, 16 | 17 | /// The receive half of the channel used to listen for shutdown. 18 | notify: broadcast::Receiver<()>, 19 | } 20 | 21 | impl Shutdown { 22 | /// Create a new `Shutdown` backed by the given `broadcast::Receiver`. 23 | pub(crate) fn new(notify: broadcast::Receiver<()>) -> Shutdown { 24 | Shutdown { 25 | is_shutdown: false, 26 | notify, 27 | } 28 | } 29 | 30 | /// Returns `true` if the shutdown signal has been received. 31 | pub(crate) fn is_shutdown(&self) -> bool { 32 | self.is_shutdown 33 | } 34 | 35 | /// Receive the shutdown notice, waiting if necessary. 36 | pub(crate) async fn recv(&mut self) { 37 | // If the shutdown signal has already been received, then return 38 | // immediately. 39 | if self.is_shutdown { 40 | return; 41 | } 42 | 43 | // Cannot receive a "lag error" as only one value is ever sent. 44 | let _ = self.notify.recv().await; 45 | 46 | // Remember that the signal has been received. 47 | self.is_shutdown = true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/buffered_client.rs: -------------------------------------------------------------------------------- 1 | use mini_redis::{ 2 | clients::{BufferedClient, Client}, 3 | server, 4 | }; 5 | use std::net::SocketAddr; 6 | use tokio::net::TcpListener; 7 | use tokio::task::JoinHandle; 8 | 9 | /// A basic "hello world" style test. A server instance is started in a 10 | /// background task. A client instance is then established and used to initialize 11 | /// the buffer. Set and get commands are sent to the server. The response is 12 | /// then evaluated. 13 | #[tokio::test] 14 | async fn pool_key_value_get_set() { 15 | let (addr, _) = start_server().await; 16 | 17 | let client = Client::connect(addr).await.unwrap(); 18 | let mut client = BufferedClient::buffer(client); 19 | 20 | client.set("hello", "world".into()).await.unwrap(); 21 | 22 | let value = client.get("hello").await.unwrap().unwrap(); 23 | assert_eq!(b"world", &value[..]) 24 | } 25 | 26 | async fn start_server() -> (SocketAddr, JoinHandle<()>) { 27 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 28 | let addr = listener.local_addr().unwrap(); 29 | 30 | let handle = tokio::spawn(async move { server::run(listener, tokio::signal::ctrl_c()).await }); 31 | 32 | (addr, handle) 33 | } 34 | -------------------------------------------------------------------------------- /tests/client.rs: -------------------------------------------------------------------------------- 1 | use mini_redis::{clients::Client, server}; 2 | use std::net::SocketAddr; 3 | use tokio::net::TcpListener; 4 | use tokio::task::JoinHandle; 5 | 6 | /// A PING PONG test without message provided. 7 | /// It should return "PONG". 8 | #[tokio::test] 9 | async fn ping_pong_without_message() { 10 | let (addr, _) = start_server().await; 11 | let mut client = Client::connect(addr).await.unwrap(); 12 | 13 | let pong = client.ping(None).await.unwrap(); 14 | assert_eq!(b"PONG", &pong[..]); 15 | } 16 | 17 | /// A PING PONG test with message provided. 18 | /// It should return the message. 19 | #[tokio::test] 20 | async fn ping_pong_with_message() { 21 | let (addr, _) = start_server().await; 22 | let mut client = Client::connect(addr).await.unwrap(); 23 | 24 | let pong = client.ping(Some("你好世界".into())).await.unwrap(); 25 | assert_eq!("你好世界".as_bytes(), &pong[..]); 26 | } 27 | 28 | /// A basic "hello world" style test. A server instance is started in a 29 | /// background task. A client instance is then established and set and get 30 | /// commands are sent to the server. The response is then evaluated 31 | #[tokio::test] 32 | async fn key_value_get_set() { 33 | let (addr, _) = start_server().await; 34 | 35 | let mut client = Client::connect(addr).await.unwrap(); 36 | client.set("hello", "world".into()).await.unwrap(); 37 | 38 | let value = client.get("hello").await.unwrap().unwrap(); 39 | assert_eq!(b"world", &value[..]) 40 | } 41 | 42 | /// similar to the "hello world" style test, But this time 43 | /// a single channel subscription will be tested instead 44 | #[tokio::test] 45 | async fn receive_message_subscribed_channel() { 46 | let (addr, _) = start_server().await; 47 | 48 | let client = Client::connect(addr).await.unwrap(); 49 | let mut subscriber = client.subscribe(vec!["hello".into()]).await.unwrap(); 50 | 51 | tokio::spawn(async move { 52 | let mut client = Client::connect(addr).await.unwrap(); 53 | client.publish("hello", "world".into()).await.unwrap() 54 | }); 55 | 56 | let message = subscriber.next_message().await.unwrap().unwrap(); 57 | assert_eq!("hello", &message.channel); 58 | assert_eq!(b"world", &message.content[..]) 59 | } 60 | 61 | /// test that a client gets messages from multiple subscribed channels 62 | #[tokio::test] 63 | async fn receive_message_multiple_subscribed_channels() { 64 | let (addr, _) = start_server().await; 65 | 66 | let client = Client::connect(addr).await.unwrap(); 67 | let mut subscriber = client 68 | .subscribe(vec!["hello".into(), "world".into()]) 69 | .await 70 | .unwrap(); 71 | 72 | tokio::spawn(async move { 73 | let mut client = Client::connect(addr).await.unwrap(); 74 | client.publish("hello", "world".into()).await.unwrap() 75 | }); 76 | 77 | let message1 = subscriber.next_message().await.unwrap().unwrap(); 78 | assert_eq!("hello", &message1.channel); 79 | assert_eq!(b"world", &message1.content[..]); 80 | 81 | tokio::spawn(async move { 82 | let mut client = Client::connect(addr).await.unwrap(); 83 | client.publish("world", "howdy?".into()).await.unwrap() 84 | }); 85 | 86 | let message2 = subscriber.next_message().await.unwrap().unwrap(); 87 | assert_eq!("world", &message2.channel); 88 | assert_eq!(b"howdy?", &message2.content[..]) 89 | } 90 | 91 | /// test that a client accurately removes its own subscribed channel list 92 | /// when unsubscribing to all subscribed channels by submitting an empty vec 93 | #[tokio::test] 94 | async fn unsubscribes_from_channels() { 95 | let (addr, _) = start_server().await; 96 | 97 | let client = Client::connect(addr).await.unwrap(); 98 | let mut subscriber = client 99 | .subscribe(vec!["hello".into(), "world".into()]) 100 | .await 101 | .unwrap(); 102 | 103 | subscriber.unsubscribe(&[]).await.unwrap(); 104 | assert_eq!(subscriber.get_subscribed().len(), 0); 105 | } 106 | 107 | async fn start_server() -> (SocketAddr, JoinHandle<()>) { 108 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 109 | let addr = listener.local_addr().unwrap(); 110 | 111 | let handle = tokio::spawn(async move { server::run(listener, tokio::signal::ctrl_c()).await }); 112 | 113 | (addr, handle) 114 | } 115 | -------------------------------------------------------------------------------- /tests/server.rs: -------------------------------------------------------------------------------- 1 | use mini_redis::server; 2 | 3 | use std::net::SocketAddr; 4 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 5 | use tokio::net::{TcpListener, TcpStream}; 6 | use tokio::time::{self, Duration}; 7 | 8 | /// A basic "hello world" style test. A server instance is started in a 9 | /// background task. A client TCP connection is then established and raw redis 10 | /// commands are sent to the server. The response is evaluated at the byte 11 | /// level. 12 | #[tokio::test] 13 | async fn key_value_get_set() { 14 | let addr = start_server().await; 15 | 16 | // Establish a connection to the server 17 | let mut stream = TcpStream::connect(addr).await.unwrap(); 18 | 19 | // Get a key, data is missing 20 | stream 21 | .write_all(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n") 22 | .await 23 | .unwrap(); 24 | 25 | // Read nil response 26 | let mut response = [0; 5]; 27 | stream.read_exact(&mut response).await.unwrap(); 28 | assert_eq!(b"$-1\r\n", &response); 29 | 30 | // Set a key 31 | stream 32 | .write_all(b"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n") 33 | .await 34 | .unwrap(); 35 | 36 | // Read OK 37 | let mut response = [0; 5]; 38 | stream.read_exact(&mut response).await.unwrap(); 39 | assert_eq!(b"+OK\r\n", &response); 40 | 41 | // Get the key, data is present 42 | stream 43 | .write_all(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n") 44 | .await 45 | .unwrap(); 46 | 47 | // Shutdown the write half 48 | stream.shutdown().await.unwrap(); 49 | 50 | // Read "world" response 51 | let mut response = [0; 11]; 52 | stream.read_exact(&mut response).await.unwrap(); 53 | assert_eq!(b"$5\r\nworld\r\n", &response); 54 | 55 | // Receive `None` 56 | assert_eq!(0, stream.read(&mut response).await.unwrap()); 57 | } 58 | 59 | /// Similar to the basic key-value test, however, this time timeouts will be 60 | /// tested. This test demonstrates how to test time related behavior. 61 | /// 62 | /// When writing tests, it is useful to remove sources of non-determinism. Time 63 | /// is a source of non-determinism. Here, we "pause" time using the 64 | /// `time::pause()` function. This function is available with the `test-util` 65 | /// feature flag. This allows us to deterministically control how time appears 66 | /// to advance to the application. 67 | #[tokio::test] 68 | async fn key_value_timeout() { 69 | tokio::time::pause(); 70 | 71 | let addr = start_server().await; 72 | 73 | // Establish a connection to the server 74 | let mut stream = TcpStream::connect(addr).await.unwrap(); 75 | 76 | // Set a key 77 | stream 78 | .write_all( 79 | b"*5\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n\ 80 | +EX\r\n:1\r\n", 81 | ) 82 | .await 83 | .unwrap(); 84 | 85 | let mut response = [0; 5]; 86 | 87 | // Read OK 88 | stream.read_exact(&mut response).await.unwrap(); 89 | 90 | assert_eq!(b"+OK\r\n", &response); 91 | 92 | // Get the key, data is present 93 | stream 94 | .write_all(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n") 95 | .await 96 | .unwrap(); 97 | 98 | // Read "world" response 99 | let mut response = [0; 11]; 100 | 101 | stream.read_exact(&mut response).await.unwrap(); 102 | 103 | assert_eq!(b"$5\r\nworld\r\n", &response); 104 | 105 | // Wait for the key to expire 106 | time::advance(Duration::from_secs(1)).await; 107 | 108 | // Get a key, data is missing 109 | stream 110 | .write_all(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n") 111 | .await 112 | .unwrap(); 113 | 114 | // Read nil response 115 | let mut response = [0; 5]; 116 | 117 | stream.read_exact(&mut response).await.unwrap(); 118 | 119 | assert_eq!(b"$-1\r\n", &response); 120 | } 121 | 122 | #[tokio::test] 123 | async fn pub_sub() { 124 | let addr = start_server().await; 125 | 126 | let mut publisher = TcpStream::connect(addr).await.unwrap(); 127 | 128 | // Publish a message, there are no subscribers yet so the server will 129 | // return `0`. 130 | publisher 131 | .write_all(b"*3\r\n$7\r\nPUBLISH\r\n$5\r\nhello\r\n$5\r\nworld\r\n") 132 | .await 133 | .unwrap(); 134 | 135 | let mut response = [0; 4]; 136 | publisher.read_exact(&mut response).await.unwrap(); 137 | assert_eq!(b":0\r\n", &response); 138 | 139 | // Create a subscriber. This subscriber will only subscribe to the `hello` 140 | // channel. 141 | let mut sub1 = TcpStream::connect(addr).await.unwrap(); 142 | sub1.write_all(b"*2\r\n$9\r\nSUBSCRIBE\r\n$5\r\nhello\r\n") 143 | .await 144 | .unwrap(); 145 | 146 | // Read the subscribe response 147 | let mut response = [0; 34]; 148 | sub1.read_exact(&mut response).await.unwrap(); 149 | assert_eq!( 150 | &b"*3\r\n$9\r\nsubscribe\r\n$5\r\nhello\r\n:1\r\n"[..], 151 | &response[..] 152 | ); 153 | 154 | // Publish a message, there now is a subscriber 155 | publisher 156 | .write_all(b"*3\r\n$7\r\nPUBLISH\r\n$5\r\nhello\r\n$5\r\nworld\r\n") 157 | .await 158 | .unwrap(); 159 | 160 | let mut response = [0; 4]; 161 | publisher.read_exact(&mut response).await.unwrap(); 162 | assert_eq!(b":1\r\n", &response); 163 | 164 | // The first subscriber received the message 165 | let mut response = [0; 39]; 166 | sub1.read_exact(&mut response).await.unwrap(); 167 | assert_eq!( 168 | &b"*3\r\n$7\r\nmessage\r\n$5\r\nhello\r\n$5\r\nworld\r\n"[..], 169 | &response[..] 170 | ); 171 | 172 | // Create a second subscriber 173 | // 174 | // This subscriber will be subscribed to both `hello` and `foo` 175 | let mut sub2 = TcpStream::connect(addr).await.unwrap(); 176 | sub2.write_all(b"*3\r\n$9\r\nSUBSCRIBE\r\n$5\r\nhello\r\n$3\r\nfoo\r\n") 177 | .await 178 | .unwrap(); 179 | 180 | // Read the subscribe response 181 | let mut response = [0; 34]; 182 | sub2.read_exact(&mut response).await.unwrap(); 183 | assert_eq!( 184 | &b"*3\r\n$9\r\nsubscribe\r\n$5\r\nhello\r\n:1\r\n"[..], 185 | &response[..] 186 | ); 187 | let mut response = [0; 32]; 188 | sub2.read_exact(&mut response).await.unwrap(); 189 | assert_eq!( 190 | &b"*3\r\n$9\r\nsubscribe\r\n$3\r\nfoo\r\n:2\r\n"[..], 191 | &response[..] 192 | ); 193 | 194 | // Publish another message on `hello`, there are two subscribers 195 | publisher 196 | .write_all(b"*3\r\n$7\r\nPUBLISH\r\n$5\r\nhello\r\n$5\r\njazzy\r\n") 197 | .await 198 | .unwrap(); 199 | 200 | let mut response = [0; 4]; 201 | publisher.read_exact(&mut response).await.unwrap(); 202 | assert_eq!(b":2\r\n", &response); 203 | 204 | // Publish a message on `foo`, there is only one subscriber 205 | publisher 206 | .write_all(b"*3\r\n$7\r\nPUBLISH\r\n$3\r\nfoo\r\n$3\r\nbar\r\n") 207 | .await 208 | .unwrap(); 209 | 210 | let mut response = [0; 4]; 211 | publisher.read_exact(&mut response).await.unwrap(); 212 | assert_eq!(b":1\r\n", &response); 213 | 214 | // The first subscriber received the message 215 | let mut response = [0; 39]; 216 | sub1.read_exact(&mut response).await.unwrap(); 217 | assert_eq!( 218 | &b"*3\r\n$7\r\nmessage\r\n$5\r\nhello\r\n$5\r\njazzy\r\n"[..], 219 | &response[..] 220 | ); 221 | 222 | // The second subscriber received the message 223 | let mut response = [0; 39]; 224 | sub2.read_exact(&mut response).await.unwrap(); 225 | assert_eq!( 226 | &b"*3\r\n$7\r\nmessage\r\n$5\r\nhello\r\n$5\r\njazzy\r\n"[..], 227 | &response[..] 228 | ); 229 | 230 | // The first subscriber **did not** receive the second message 231 | let mut response = [0; 1]; 232 | time::timeout(Duration::from_millis(100), sub1.read(&mut response)) 233 | .await 234 | .unwrap_err(); 235 | 236 | // The second subscriber **did** receive the message 237 | let mut response = [0; 35]; 238 | sub2.read_exact(&mut response).await.unwrap(); 239 | assert_eq!( 240 | &b"*3\r\n$7\r\nmessage\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"[..], 241 | &response[..] 242 | ); 243 | } 244 | 245 | #[tokio::test] 246 | async fn manage_subscription() { 247 | let addr = start_server().await; 248 | 249 | let mut publisher = TcpStream::connect(addr).await.unwrap(); 250 | 251 | // Create a subscriber 252 | let mut sub = TcpStream::connect(addr).await.unwrap(); 253 | sub.write_all(b"*2\r\n$9\r\nSUBSCRIBE\r\n$5\r\nhello\r\n") 254 | .await 255 | .unwrap(); 256 | 257 | // Read the subscribe response 258 | let mut response = [0; 34]; 259 | sub.read_exact(&mut response).await.unwrap(); 260 | assert_eq!( 261 | &b"*3\r\n$9\r\nsubscribe\r\n$5\r\nhello\r\n:1\r\n"[..], 262 | &response[..] 263 | ); 264 | 265 | // Update subscription to add `foo` 266 | sub.write_all(b"*2\r\n$9\r\nSUBSCRIBE\r\n$3\r\nfoo\r\n") 267 | .await 268 | .unwrap(); 269 | 270 | let mut response = [0; 32]; 271 | sub.read_exact(&mut response).await.unwrap(); 272 | assert_eq!( 273 | &b"*3\r\n$9\r\nsubscribe\r\n$3\r\nfoo\r\n:2\r\n"[..], 274 | &response[..] 275 | ); 276 | 277 | // Update subscription to remove `hello` 278 | sub.write_all(b"*2\r\n$11\r\nUNSUBSCRIBE\r\n$5\r\nhello\r\n") 279 | .await 280 | .unwrap(); 281 | 282 | let mut response = [0; 37]; 283 | sub.read_exact(&mut response).await.unwrap(); 284 | assert_eq!( 285 | &b"*3\r\n$11\r\nunsubscribe\r\n$5\r\nhello\r\n:1\r\n"[..], 286 | &response[..] 287 | ); 288 | 289 | // Publish a message to `hello` and then a message to `foo` 290 | publisher 291 | .write_all(b"*3\r\n$7\r\nPUBLISH\r\n$5\r\nhello\r\n$5\r\nworld\r\n") 292 | .await 293 | .unwrap(); 294 | let mut response = [0; 4]; 295 | publisher.read_exact(&mut response).await.unwrap(); 296 | assert_eq!(b":0\r\n", &response); 297 | 298 | publisher 299 | .write_all(b"*3\r\n$7\r\nPUBLISH\r\n$3\r\nfoo\r\n$3\r\nbar\r\n") 300 | .await 301 | .unwrap(); 302 | let mut response = [0; 4]; 303 | publisher.read_exact(&mut response).await.unwrap(); 304 | assert_eq!(b":1\r\n", &response); 305 | 306 | // Receive the message 307 | // The second subscriber **did** receive the message 308 | let mut response = [0; 35]; 309 | sub.read_exact(&mut response).await.unwrap(); 310 | assert_eq!( 311 | &b"*3\r\n$7\r\nmessage\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"[..], 312 | &response[..] 313 | ); 314 | 315 | // No more messages 316 | let mut response = [0; 1]; 317 | time::timeout(Duration::from_millis(100), sub.read(&mut response)) 318 | .await 319 | .unwrap_err(); 320 | 321 | // Unsubscribe from all channels 322 | sub.write_all(b"*1\r\n$11\r\nunsubscribe\r\n") 323 | .await 324 | .unwrap(); 325 | 326 | let mut response = [0; 35]; 327 | sub.read_exact(&mut response).await.unwrap(); 328 | assert_eq!( 329 | &b"*3\r\n$11\r\nunsubscribe\r\n$3\r\nfoo\r\n:0\r\n"[..], 330 | &response[..] 331 | ); 332 | } 333 | 334 | // In this case we test that server Responds with an Error message if a client 335 | // sends an unknown command 336 | #[tokio::test] 337 | async fn send_error_unknown_command() { 338 | let addr = start_server().await; 339 | 340 | // Establish a connection to the server 341 | let mut stream = TcpStream::connect(addr).await.unwrap(); 342 | 343 | // Get a key, data is missing 344 | stream 345 | .write_all(b"*2\r\n$3\r\nFOO\r\n$5\r\nhello\r\n") 346 | .await 347 | .unwrap(); 348 | 349 | let mut response = [0; 28]; 350 | 351 | stream.read_exact(&mut response).await.unwrap(); 352 | 353 | assert_eq!(b"-ERR unknown command \'foo\'\r\n", &response); 354 | } 355 | 356 | // In this case we test that server Responds with an Error message if a client 357 | // sends an GET or SET command after a SUBSCRIBE 358 | #[tokio::test] 359 | async fn send_error_get_set_after_subscribe() { 360 | let addr = start_server().await; 361 | 362 | let mut stream = TcpStream::connect(addr).await.unwrap(); 363 | 364 | // send SUBSCRIBE command 365 | stream 366 | .write_all(b"*2\r\n$9\r\nsubscribe\r\n$5\r\nhello\r\n") 367 | .await 368 | .unwrap(); 369 | 370 | let mut response = [0; 34]; 371 | 372 | stream.read_exact(&mut response).await.unwrap(); 373 | 374 | assert_eq!( 375 | &b"*3\r\n$9\r\nsubscribe\r\n$5\r\nhello\r\n:1\r\n"[..], 376 | &response[..] 377 | ); 378 | 379 | stream 380 | .write_all(b"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n") 381 | .await 382 | .unwrap(); 383 | 384 | let mut response = [0; 28]; 385 | 386 | stream.read_exact(&mut response).await.unwrap(); 387 | assert_eq!(b"-ERR unknown command \'set\'\r\n", &response); 388 | 389 | stream 390 | .write_all(b"*2\r\n$3\r\nGET\r\n$5\r\nhello\r\n") 391 | .await 392 | .unwrap(); 393 | 394 | let mut response = [0; 28]; 395 | 396 | stream.read_exact(&mut response).await.unwrap(); 397 | assert_eq!(b"-ERR unknown command \'get\'\r\n", &response); 398 | } 399 | 400 | async fn start_server() -> SocketAddr { 401 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 402 | let addr = listener.local_addr().unwrap(); 403 | 404 | tokio::spawn(async move { server::run(listener, tokio::signal::ctrl_c()).await }); 405 | 406 | addr 407 | } 408 | --------------------------------------------------------------------------------