mirror of
https://github.com/sunface/rust-by-practice.git
synced 2025-08-12 06:24:44 +00:00
add rust lang to repo by add assets/mini-redis
This commit is contained in:
93
zh-CN/assets/mini-redis/src/cmd/get.rs
Normal file
93
zh-CN/assets/mini-redis/src/cmd/get.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use crate::{Connection, Db, Frame, Parse};
|
||||
|
||||
use bytes::Bytes;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
/// Get the value of key.
|
||||
///
|
||||
/// If the key does not exist the special value nil is returned. An error is
|
||||
/// returned if the value stored at key is not a string, because GET only
|
||||
/// handles string values.
|
||||
#[derive(Debug)]
|
||||
pub struct Get {
|
||||
/// Name of the key to get
|
||||
key: String,
|
||||
}
|
||||
|
||||
impl Get {
|
||||
/// Create a new `Get` command which fetches `key`.
|
||||
pub fn new(key: impl ToString) -> Get {
|
||||
Get {
|
||||
key: key.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the key
|
||||
pub fn key(&self) -> &str {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Parse a `Get` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `GET` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the `Get` value on success. If the frame is malformed, `Err` is
|
||||
/// returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing two entries.
|
||||
///
|
||||
/// ```text
|
||||
/// GET key
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result<Get> {
|
||||
// The `GET` string has already been consumed. The next value is the
|
||||
// name of the key to get. If the next value is not a string or the
|
||||
// input is fully consumed, then an error is returned.
|
||||
let key = parse.next_string()?;
|
||||
|
||||
Ok(Get { key })
|
||||
}
|
||||
|
||||
/// Apply the `Get` command to the specified `Db` instance.
|
||||
///
|
||||
/// The response is written to `dst`. This is called by the server in order
|
||||
/// to execute a received command.
|
||||
#[instrument(skip(self, db, dst))]
|
||||
pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> {
|
||||
// Get the value from the shared database state
|
||||
let response = if let Some(value) = db.get(&self.key) {
|
||||
// If a value is present, it is written to the client in "bulk"
|
||||
// format.
|
||||
Frame::Bulk(value)
|
||||
} else {
|
||||
// If there is no value, `Null` is written.
|
||||
Frame::Null
|
||||
};
|
||||
|
||||
debug!(?response);
|
||||
|
||||
// Write the response back to the client
|
||||
dst.write_frame(&response).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding a `Get` command to send to
|
||||
/// the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("get".as_bytes()));
|
||||
frame.push_bulk(Bytes::from(self.key.into_bytes()));
|
||||
frame
|
||||
}
|
||||
}
|
116
zh-CN/assets/mini-redis/src/cmd/mod.rs
Normal file
116
zh-CN/assets/mini-redis/src/cmd/mod.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
mod get;
|
||||
pub use get::Get;
|
||||
|
||||
mod publish;
|
||||
pub use publish::Publish;
|
||||
|
||||
mod set;
|
||||
pub use set::Set;
|
||||
|
||||
mod subscribe;
|
||||
pub use subscribe::{Subscribe, Unsubscribe};
|
||||
|
||||
mod unknown;
|
||||
pub use unknown::Unknown;
|
||||
|
||||
use crate::{Connection, Db, Frame, Parse, ParseError, Shutdown};
|
||||
|
||||
/// Enumeration of supported Redis commands.
|
||||
///
|
||||
/// Methods called on `Command` are delegated to the command implementation.
|
||||
#[derive(Debug)]
|
||||
pub enum Command {
|
||||
Get(Get),
|
||||
Publish(Publish),
|
||||
Set(Set),
|
||||
Subscribe(Subscribe),
|
||||
Unsubscribe(Unsubscribe),
|
||||
Unknown(Unknown),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Parse a command from a received frame.
|
||||
///
|
||||
/// The `Frame` must represent a Redis command supported by `mini-redis` and
|
||||
/// be the array variant.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the command value is returned, otherwise, `Err` is returned.
|
||||
pub fn from_frame(frame: Frame) -> crate::Result<Command> {
|
||||
// The frame value is decorated with `Parse`. `Parse` provides a
|
||||
// "cursor" like API which makes parsing the command easier.
|
||||
//
|
||||
// The frame value must be an array variant. Any other frame variants
|
||||
// result in an error being returned.
|
||||
let mut parse = Parse::new(frame)?;
|
||||
|
||||
// All redis commands begin with the command name as a string. The name
|
||||
// is read and converted to lower cases in order to do case sensitive
|
||||
// matching.
|
||||
let command_name = parse.next_string()?.to_lowercase();
|
||||
|
||||
// Match the command name, delegating the rest of the parsing to the
|
||||
// specific command.
|
||||
let command = match &command_name[..] {
|
||||
"get" => Command::Get(Get::parse_frames(&mut parse)?),
|
||||
"publish" => Command::Publish(Publish::parse_frames(&mut parse)?),
|
||||
"set" => Command::Set(Set::parse_frames(&mut parse)?),
|
||||
"subscribe" => Command::Subscribe(Subscribe::parse_frames(&mut parse)?),
|
||||
"unsubscribe" => Command::Unsubscribe(Unsubscribe::parse_frames(&mut parse)?),
|
||||
_ => {
|
||||
// The command is not recognized and an Unknown command is
|
||||
// returned.
|
||||
//
|
||||
// `return` is called here to skip the `finish()` call below. As
|
||||
// the command is not recognized, there is most likely
|
||||
// unconsumed fields remaining in the `Parse` instance.
|
||||
return Ok(Command::Unknown(Unknown::new(command_name)));
|
||||
}
|
||||
};
|
||||
|
||||
// Check if there is any remaining unconsumed fields in the `Parse`
|
||||
// value. If fields remain, this indicates an unexpected frame format
|
||||
// and an error is returned.
|
||||
parse.finish()?;
|
||||
|
||||
// The command has been successfully parsed
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
/// Apply the command to the specified `Db` instance.
|
||||
///
|
||||
/// The response is written to `dst`. This is called by the server in order
|
||||
/// to execute a received command.
|
||||
pub(crate) async fn apply(
|
||||
self,
|
||||
db: &Db,
|
||||
dst: &mut Connection,
|
||||
shutdown: &mut Shutdown,
|
||||
) -> crate::Result<()> {
|
||||
use Command::*;
|
||||
|
||||
match self {
|
||||
Get(cmd) => cmd.apply(db, dst).await,
|
||||
Publish(cmd) => cmd.apply(db, dst).await,
|
||||
Set(cmd) => cmd.apply(db, dst).await,
|
||||
Subscribe(cmd) => cmd.apply(db, dst, shutdown).await,
|
||||
Unknown(cmd) => cmd.apply(dst).await,
|
||||
// `Unsubscribe` cannot be applied. It may only be received from the
|
||||
// context of a `Subscribe` command.
|
||||
Unsubscribe(_) => Err("`Unsubscribe` is unsupported in this context".into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the command name
|
||||
pub(crate) fn get_name(&self) -> &str {
|
||||
match self {
|
||||
Command::Get(_) => "get",
|
||||
Command::Publish(_) => "pub",
|
||||
Command::Set(_) => "set",
|
||||
Command::Subscribe(_) => "subscribe",
|
||||
Command::Unsubscribe(_) => "unsubscribe",
|
||||
Command::Unknown(cmd) => cmd.get_name(),
|
||||
}
|
||||
}
|
||||
}
|
101
zh-CN/assets/mini-redis/src/cmd/publish.rs
Normal file
101
zh-CN/assets/mini-redis/src/cmd/publish.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use crate::{Connection, Db, Frame, Parse};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
/// Posts a message to the given channel.
|
||||
///
|
||||
/// Send a message into a channel without any knowledge of individual consumers.
|
||||
/// Consumers may subscribe to channels in order to receive the messages.
|
||||
///
|
||||
/// Channel names have no relation to the key-value namespace. Publishing on a
|
||||
/// channel named "foo" has no relation to setting the "foo" key.
|
||||
#[derive(Debug)]
|
||||
pub struct Publish {
|
||||
/// Name of the channel on which the message should be published.
|
||||
channel: String,
|
||||
|
||||
/// The message to publish.
|
||||
message: Bytes,
|
||||
}
|
||||
|
||||
impl Publish {
|
||||
/// Create a new `Publish` command which sends `message` on `channel`.
|
||||
pub(crate) fn new(channel: impl ToString, message: Bytes) -> Publish {
|
||||
Publish {
|
||||
channel: channel.to_string(),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `Publish` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `PUBLISH` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the `Publish` value is returned. If the frame is malformed,
|
||||
/// `Err` is returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing three entries.
|
||||
///
|
||||
/// ```text
|
||||
/// PUBLISH channel message
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result<Publish> {
|
||||
// The `PUBLISH` string has already been consumed. Extract the `channel`
|
||||
// and `message` values from the frame.
|
||||
//
|
||||
// The `channel` must be a valid string.
|
||||
let channel = parse.next_string()?;
|
||||
|
||||
// The `message` is arbitrary bytes.
|
||||
let message = parse.next_bytes()?;
|
||||
|
||||
Ok(Publish { channel, message })
|
||||
}
|
||||
|
||||
/// Apply the `Publish` command to the specified `Db` instance.
|
||||
///
|
||||
/// The response is written to `dst`. This is called by the server in order
|
||||
/// to execute a received command.
|
||||
pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> {
|
||||
// The shared state contains the `tokio::sync::broadcast::Sender` for
|
||||
// all active channels. Calling `db.publish` dispatches the message into
|
||||
// the appropriate channel.
|
||||
//
|
||||
// The number of subscribers currently listening on the channel is
|
||||
// returned. This does not mean that `num_subscriber` channels will
|
||||
// receive the message. Subscribers may drop before receiving the
|
||||
// message. Given this, `num_subscribers` should only be used as a
|
||||
// "hint".
|
||||
let num_subscribers = db.publish(&self.channel, self.message);
|
||||
|
||||
// The number of subscribers is returned as the response to the publish
|
||||
// request.
|
||||
let response = Frame::Integer(num_subscribers as u64);
|
||||
|
||||
// Write the frame to the client.
|
||||
dst.write_frame(&response).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding a `Publish` command to send
|
||||
/// to the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("publish".as_bytes()));
|
||||
frame.push_bulk(Bytes::from(self.channel.into_bytes()));
|
||||
frame.push_bulk(self.message);
|
||||
|
||||
frame
|
||||
}
|
||||
}
|
161
zh-CN/assets/mini-redis/src/cmd/set.rs
Normal file
161
zh-CN/assets/mini-redis/src/cmd/set.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use crate::cmd::{Parse, ParseError};
|
||||
use crate::{Connection, Db, Frame};
|
||||
|
||||
use bytes::Bytes;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
/// Set `key` to hold the string `value`.
|
||||
///
|
||||
/// If `key` already holds a value, it is overwritten, regardless of its type.
|
||||
/// Any previous time to live associated with the key is discarded on successful
|
||||
/// SET operation.
|
||||
///
|
||||
/// # Options
|
||||
///
|
||||
/// Currently, the following options are supported:
|
||||
///
|
||||
/// * EX `seconds` -- Set the specified expire time, in seconds.
|
||||
/// * PX `milliseconds` -- Set the specified expire time, in milliseconds.
|
||||
#[derive(Debug)]
|
||||
pub struct Set {
|
||||
/// the lookup key
|
||||
key: String,
|
||||
|
||||
/// the value to be stored
|
||||
value: Bytes,
|
||||
|
||||
/// When to expire the key
|
||||
expire: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Set {
|
||||
/// Create a new `Set` command which sets `key` to `value`.
|
||||
///
|
||||
/// If `expire` is `Some`, the value should expire after the specified
|
||||
/// duration.
|
||||
pub fn new(key: impl ToString, value: Bytes, expire: Option<Duration>) -> Set {
|
||||
Set {
|
||||
key: key.to_string(),
|
||||
value,
|
||||
expire,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the key
|
||||
pub fn key(&self) -> &str {
|
||||
&self.key
|
||||
}
|
||||
|
||||
/// Get the value
|
||||
pub fn value(&self) -> &Bytes {
|
||||
&self.value
|
||||
}
|
||||
|
||||
/// Get the expire
|
||||
pub fn expire(&self) -> Option<Duration> {
|
||||
self.expire
|
||||
}
|
||||
|
||||
/// Parse a `Set` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `SET` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the `Set` value on success. If the frame is malformed, `Err` is
|
||||
/// returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing at least 3 entries.
|
||||
///
|
||||
/// ```text
|
||||
/// SET key value [EX seconds|PX milliseconds]
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result<Set> {
|
||||
use ParseError::EndOfStream;
|
||||
|
||||
// Read the key to set. This is a required field
|
||||
let key = parse.next_string()?;
|
||||
|
||||
// Read the value to set. This is a required field.
|
||||
let value = parse.next_bytes()?;
|
||||
|
||||
// The expiration is optional. If nothing else follows, then it is
|
||||
// `None`.
|
||||
let mut expire = None;
|
||||
|
||||
// Attempt to parse another string.
|
||||
match parse.next_string() {
|
||||
Ok(s) if s.to_uppercase() == "EX" => {
|
||||
// An expiration is specified in seconds. The next value is an
|
||||
// integer.
|
||||
let secs = parse.next_int()?;
|
||||
expire = Some(Duration::from_secs(secs));
|
||||
}
|
||||
Ok(s) if s.to_uppercase() == "PX" => {
|
||||
// An expiration is specified in milliseconds. The next value is
|
||||
// an integer.
|
||||
let ms = parse.next_int()?;
|
||||
expire = Some(Duration::from_millis(ms));
|
||||
}
|
||||
// Currently, mini-redis does not support any of the other SET
|
||||
// options. An error here results in the connection being
|
||||
// terminated. Other connections will continue to operate normally.
|
||||
Ok(_) => return Err("currently `SET` only supports the expiration option".into()),
|
||||
// The `EndOfStream` error indicates there is no further data to
|
||||
// parse. In this case, it is a normal run time situation and
|
||||
// indicates there are no specified `SET` options.
|
||||
Err(EndOfStream) => {}
|
||||
// All other errors are bubbled up, resulting in the connection
|
||||
// being terminated.
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
|
||||
Ok(Set { key, value, expire })
|
||||
}
|
||||
|
||||
/// Apply the `Set` command to the specified `Db` instance.
|
||||
///
|
||||
/// The response is written to `dst`. This is called by the server in order
|
||||
/// to execute a received command.
|
||||
#[instrument(skip(self, db, dst))]
|
||||
pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> {
|
||||
// Set the value in the shared database state.
|
||||
db.set(self.key, self.value, self.expire);
|
||||
|
||||
// Create a success response and write it to `dst`.
|
||||
let response = Frame::Simple("OK".to_string());
|
||||
debug!(?response);
|
||||
dst.write_frame(&response).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding a `Set` command to send to
|
||||
/// the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("set".as_bytes()));
|
||||
frame.push_bulk(Bytes::from(self.key.into_bytes()));
|
||||
frame.push_bulk(self.value);
|
||||
if let Some(ms) = self.expire {
|
||||
// Expirations in Redis procotol can be specified in two ways
|
||||
// 1. SET key value EX seconds
|
||||
// 2. SET key value PX milliseconds
|
||||
// We the second option because it allows greater precision and
|
||||
// src/bin/cli.rs parses the expiration argument as milliseconds
|
||||
// in duration_from_ms_str()
|
||||
frame.push_bulk(Bytes::from("px".as_bytes()));
|
||||
frame.push_int(ms.as_millis() as u64);
|
||||
}
|
||||
frame
|
||||
}
|
||||
}
|
351
zh-CN/assets/mini-redis/src/cmd/subscribe.rs
Normal file
351
zh-CN/assets/mini-redis/src/cmd/subscribe.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
use crate::cmd::{Parse, ParseError, Unknown};
|
||||
use crate::{Command, Connection, Db, Frame, Shutdown};
|
||||
|
||||
use bytes::Bytes;
|
||||
use std::pin::Pin;
|
||||
use tokio::select;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio_stream::{Stream, StreamExt, StreamMap};
|
||||
|
||||
/// Subscribes the client to one or more channels.
|
||||
///
|
||||
/// Once the client enters the subscribed state, it is not supposed to issue any
|
||||
/// other commands, except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE,
|
||||
/// PUNSUBSCRIBE, PING and QUIT commands.
|
||||
#[derive(Debug)]
|
||||
pub struct Subscribe {
|
||||
channels: Vec<String>,
|
||||
}
|
||||
|
||||
/// Unsubscribes the client from one or more channels.
|
||||
///
|
||||
/// When no channels are specified, the client is unsubscribed from all the
|
||||
/// previously subscribed channels.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Unsubscribe {
|
||||
channels: Vec<String>,
|
||||
}
|
||||
|
||||
/// Stream of messages. The stream receives messages from the
|
||||
/// `broadcast::Receiver`. We use `stream!` to create a `Stream` that consumes
|
||||
/// messages. Because `stream!` values cannot be named, we box the stream using
|
||||
/// a trait object.
|
||||
type Messages = Pin<Box<dyn Stream<Item = Bytes> + Send>>;
|
||||
|
||||
impl Subscribe {
|
||||
/// Creates a new `Subscribe` command to listen on the specified channels.
|
||||
pub(crate) fn new(channels: &[String]) -> Subscribe {
|
||||
Subscribe {
|
||||
channels: channels.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `Subscribe` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `SUBSCRIBE` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the `Subscribe` value is returned. If the frame is
|
||||
/// malformed, `Err` is returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing two or more entries.
|
||||
///
|
||||
/// ```text
|
||||
/// SUBSCRIBE channel [channel ...]
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result<Subscribe> {
|
||||
use ParseError::EndOfStream;
|
||||
|
||||
// The `SUBSCRIBE` string has already been consumed. At this point,
|
||||
// there is one or more strings remaining in `parse`. These represent
|
||||
// the channels to subscribe to.
|
||||
//
|
||||
// Extract the first string. If there is none, the the frame is
|
||||
// malformed and the error is bubbled up.
|
||||
let mut channels = vec![parse.next_string()?];
|
||||
|
||||
// Now, the remainder of the frame is consumed. Each value must be a
|
||||
// string or the frame is malformed. Once all values in the frame have
|
||||
// been consumed, the command is fully parsed.
|
||||
loop {
|
||||
match parse.next_string() {
|
||||
// A string has been consumed from the `parse`, push it into the
|
||||
// list of channels to subscribe to.
|
||||
Ok(s) => channels.push(s),
|
||||
// The `EndOfStream` error indicates there is no further data to
|
||||
// parse.
|
||||
Err(EndOfStream) => break,
|
||||
// All other errors are bubbled up, resulting in the connection
|
||||
// being terminated.
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Subscribe { channels })
|
||||
}
|
||||
|
||||
/// Apply the `Subscribe` command to the specified `Db` instance.
|
||||
///
|
||||
/// This function is the entry point and includes the initial list of
|
||||
/// channels to subscribe to. Additional `subscribe` and `unsubscribe`
|
||||
/// commands may be received from the client and the list of subscriptions
|
||||
/// are updated accordingly.
|
||||
///
|
||||
/// [here]: https://redis.io/topics/pubsub
|
||||
pub(crate) async fn apply(
|
||||
mut self,
|
||||
db: &Db,
|
||||
dst: &mut Connection,
|
||||
shutdown: &mut Shutdown,
|
||||
) -> crate::Result<()> {
|
||||
// Each individual channel subscription is handled using a
|
||||
// `sync::broadcast` channel. Messages are then fanned out to all
|
||||
// clients currently subscribed to the channels.
|
||||
//
|
||||
// An individual client may subscribe to multiple channels and may
|
||||
// dynamically add and remove channels from its subscription set. To
|
||||
// handle this, a `StreamMap` is used to track active subscriptions. The
|
||||
// `StreamMap` merges messages from individual broadcast channels as
|
||||
// they are received.
|
||||
let mut subscriptions = StreamMap::new();
|
||||
|
||||
loop {
|
||||
// `self.channels` is used to track additional channels to subscribe
|
||||
// to. When new `SUBSCRIBE` commands are received during the
|
||||
// execution of `apply`, the new channels are pushed onto this vec.
|
||||
for channel_name in self.channels.drain(..) {
|
||||
subscribe_to_channel(channel_name, &mut subscriptions, db, dst).await?;
|
||||
}
|
||||
|
||||
// Wait for one of the following to happen:
|
||||
//
|
||||
// - Receive a message from one of the subscribed channels.
|
||||
// - Receive a subscribe or unsubscribe command from the client.
|
||||
// - A server shutdown signal.
|
||||
select! {
|
||||
// Receive messages from subscribed channels
|
||||
Some((channel_name, msg)) = subscriptions.next() => {
|
||||
dst.write_frame(&make_message_frame(channel_name, msg)).await?;
|
||||
},
|
||||
res = dst.read_frame() => {
|
||||
let frame = match res? {
|
||||
Some(frame) => frame,
|
||||
// This happens if the remote client has disconnected.
|
||||
None => return Ok(())
|
||||
};
|
||||
|
||||
handle_command(
|
||||
frame,
|
||||
&mut self.channels,
|
||||
&mut subscriptions,
|
||||
dst,
|
||||
).await?;
|
||||
},
|
||||
_ = shutdown.recv() => {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding a `Subscribe` command to send
|
||||
/// to the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("subscribe".as_bytes()));
|
||||
for channel in self.channels {
|
||||
frame.push_bulk(Bytes::from(channel.into_bytes()));
|
||||
}
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
async fn subscribe_to_channel(
|
||||
channel_name: String,
|
||||
subscriptions: &mut StreamMap<String, Messages>,
|
||||
db: &Db,
|
||||
dst: &mut Connection,
|
||||
) -> crate::Result<()> {
|
||||
let mut rx = db.subscribe(channel_name.clone());
|
||||
|
||||
// Subscribe to the channel.
|
||||
let rx = Box::pin(async_stream::stream! {
|
||||
loop {
|
||||
match rx.recv().await {
|
||||
Ok(msg) => yield msg,
|
||||
// If we lagged in consuming messages, just resume.
|
||||
Err(broadcast::error::RecvError::Lagged(_)) => {}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Track subscription in this client's subscription set.
|
||||
subscriptions.insert(channel_name.clone(), rx);
|
||||
|
||||
// Respond with the successful subscription
|
||||
let response = make_subscribe_frame(channel_name, subscriptions.len());
|
||||
dst.write_frame(&response).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle a command received while inside `Subscribe::apply`. Only subscribe
|
||||
/// and unsubscribe commands are permitted in this context.
|
||||
///
|
||||
/// Any new subscriptions are appended to `subscribe_to` instead of modifying
|
||||
/// `subscriptions`.
|
||||
async fn handle_command(
|
||||
frame: Frame,
|
||||
subscribe_to: &mut Vec<String>,
|
||||
subscriptions: &mut StreamMap<String, Messages>,
|
||||
dst: &mut Connection,
|
||||
) -> crate::Result<()> {
|
||||
// A command has been received from the client.
|
||||
//
|
||||
// Only `SUBSCRIBE` and `UNSUBSCRIBE` commands are permitted
|
||||
// in this context.
|
||||
match Command::from_frame(frame)? {
|
||||
Command::Subscribe(subscribe) => {
|
||||
// The `apply` method will subscribe to the channels we add to this
|
||||
// vector.
|
||||
subscribe_to.extend(subscribe.channels.into_iter());
|
||||
}
|
||||
Command::Unsubscribe(mut unsubscribe) => {
|
||||
// If no channels are specified, this requests unsubscribing from
|
||||
// **all** channels. To implement this, the `unsubscribe.channels`
|
||||
// vec is populated with the list of channels currently subscribed
|
||||
// to.
|
||||
if unsubscribe.channels.is_empty() {
|
||||
unsubscribe.channels = subscriptions
|
||||
.keys()
|
||||
.map(|channel_name| channel_name.to_string())
|
||||
.collect();
|
||||
}
|
||||
|
||||
for channel_name in unsubscribe.channels {
|
||||
subscriptions.remove(&channel_name);
|
||||
|
||||
let response = make_unsubscribe_frame(channel_name, subscriptions.len());
|
||||
dst.write_frame(&response).await?;
|
||||
}
|
||||
}
|
||||
command => {
|
||||
let cmd = Unknown::new(command.get_name());
|
||||
cmd.apply(dst).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates the response to a subcribe request.
|
||||
///
|
||||
/// All of these functions take the `channel_name` as a `String` instead of
|
||||
/// a `&str` since `Bytes::from` can reuse the allocation in the `String`, and
|
||||
/// taking a `&str` would require copying the data. This allows the caller to
|
||||
/// decide whether to clone the channel name or not.
|
||||
fn make_subscribe_frame(channel_name: String, num_subs: usize) -> Frame {
|
||||
let mut response = Frame::array();
|
||||
response.push_bulk(Bytes::from_static(b"subscribe"));
|
||||
response.push_bulk(Bytes::from(channel_name));
|
||||
response.push_int(num_subs as u64);
|
||||
response
|
||||
}
|
||||
|
||||
/// Creates the response to an unsubcribe request.
|
||||
fn make_unsubscribe_frame(channel_name: String, num_subs: usize) -> Frame {
|
||||
let mut response = Frame::array();
|
||||
response.push_bulk(Bytes::from_static(b"unsubscribe"));
|
||||
response.push_bulk(Bytes::from(channel_name));
|
||||
response.push_int(num_subs as u64);
|
||||
response
|
||||
}
|
||||
|
||||
/// Creates a message informing the client about a new message on a channel that
|
||||
/// the client subscribes to.
|
||||
fn make_message_frame(channel_name: String, msg: Bytes) -> Frame {
|
||||
let mut response = Frame::array();
|
||||
response.push_bulk(Bytes::from_static(b"message"));
|
||||
response.push_bulk(Bytes::from(channel_name));
|
||||
response.push_bulk(msg);
|
||||
response
|
||||
}
|
||||
|
||||
impl Unsubscribe {
|
||||
/// Create a new `Unsubscribe` command with the given `channels`.
|
||||
pub(crate) fn new(channels: &[String]) -> Unsubscribe {
|
||||
Unsubscribe {
|
||||
channels: channels.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `Unsubscribe` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `UNSUBSCRIBE` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the `Unsubscribe` value is returned. If the frame is
|
||||
/// malformed, `Err` is returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing at least one entry.
|
||||
///
|
||||
/// ```text
|
||||
/// UNSUBSCRIBE [channel [channel ...]]
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> Result<Unsubscribe, ParseError> {
|
||||
use ParseError::EndOfStream;
|
||||
|
||||
// There may be no channels listed, so start with an empty vec.
|
||||
let mut channels = vec![];
|
||||
|
||||
// Each entry in the frame must be a string or the frame is malformed.
|
||||
// Once all values in the frame have been consumed, the command is fully
|
||||
// parsed.
|
||||
loop {
|
||||
match parse.next_string() {
|
||||
// A string has been consumed from the `parse`, push it into the
|
||||
// list of channels to unsubscribe from.
|
||||
Ok(s) => channels.push(s),
|
||||
// The `EndOfStream` error indicates there is no further data to
|
||||
// parse.
|
||||
Err(EndOfStream) => break,
|
||||
// All other errors are bubbled up, resulting in the connection
|
||||
// being terminated.
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Unsubscribe { channels })
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding an `Unsubscribe` command to
|
||||
/// send to the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("unsubscribe".as_bytes()));
|
||||
|
||||
for channel in self.channels {
|
||||
frame.push_bulk(Bytes::from(channel.into_bytes()));
|
||||
}
|
||||
|
||||
frame
|
||||
}
|
||||
}
|
37
zh-CN/assets/mini-redis/src/cmd/unknown.rs
Normal file
37
zh-CN/assets/mini-redis/src/cmd/unknown.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::{Connection, Frame};
|
||||
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
/// Represents an "unknown" command. This is not a real `Redis` command.
|
||||
#[derive(Debug)]
|
||||
pub struct Unknown {
|
||||
command_name: String,
|
||||
}
|
||||
|
||||
impl Unknown {
|
||||
/// Create a new `Unknown` command which responds to unknown commands
|
||||
/// issued by clients
|
||||
pub(crate) fn new(key: impl ToString) -> Unknown {
|
||||
Unknown {
|
||||
command_name: key.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the command name
|
||||
pub(crate) fn get_name(&self) -> &str {
|
||||
&self.command_name
|
||||
}
|
||||
|
||||
/// Responds to the client, indicating the command is not recognized.
|
||||
///
|
||||
/// This usually means the command is not yet implemented by `mini-redis`.
|
||||
#[instrument(skip(self, dst))]
|
||||
pub(crate) async fn apply(self, dst: &mut Connection) -> crate::Result<()> {
|
||||
let response = Frame::Error(format!("ERR unknown command '{}'", self.command_name));
|
||||
|
||||
debug!(?response);
|
||||
|
||||
dst.write_frame(&response).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user