mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-05-21 01:43:12 +02:00
Beginnings of an async chat exercise (#627)
* beginnings of an async chat exercise * really basic solution * format
This commit is contained in:
parent
b4fb870af6
commit
caeabdae3e
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -181,6 +181,17 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chat-async"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-websockets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.24"
|
version = "0.4.24"
|
||||||
@ -1962,6 +1973,12 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1_smol"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@ -2159,9 +2176,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.27.0"
|
version = "1.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
|
checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -2171,14 +2188,14 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.0.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
|
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -2232,6 +2249,23 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-websockets"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d009a9a2a71bd44791363f42ba2dd7d47bf49aa61f7c43eabcd29a285e75a865"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.0",
|
||||||
|
"bytes",
|
||||||
|
"fastrand",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"sha1_smol",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
|
@ -4,4 +4,5 @@ members = [
|
|||||||
"src/exercises",
|
"src/exercises",
|
||||||
"src/bare-metal/useful-crates/allocator-example",
|
"src/bare-metal/useful-crates/allocator-example",
|
||||||
"src/bare-metal/useful-crates/zerocopy-example",
|
"src/bare-metal/useful-crates/zerocopy-example",
|
||||||
|
"src/exercises/concurrency/chat-async",
|
||||||
]
|
]
|
||||||
|
11
src/exercises/concurrency/chat-async/Cargo.toml
Normal file
11
src/exercises/concurrency/chat-async/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "chat-async"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures-util = "0.3.28"
|
||||||
|
http = "0.2.9"
|
||||||
|
thiserror = "1.0.40"
|
||||||
|
tokio = { version = "1.28.1", features = ["net", "macros", "time", "rt", "rt-multi-thread", "io-std", "io-util"] }
|
||||||
|
tokio-websockets = "0.3.0"
|
35
src/exercises/concurrency/chat-async/src/bin/client.rs
Normal file
35
src/exercises/concurrency/chat-async/src/bin/client.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use futures_util::SinkExt;
|
||||||
|
use http::Uri;
|
||||||
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
|
use tokio_websockets::{ClientBuilder, Message};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), tokio_websockets::Error> {
|
||||||
|
let mut ws_stream = ClientBuilder::from_uri(Uri::from_static("ws://127.0.0.1:2000"))
|
||||||
|
.connect()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let stdin = tokio::io::stdin();
|
||||||
|
let mut stdin = BufReader::new(stdin);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut line = String::new();
|
||||||
|
tokio::select! {
|
||||||
|
incoming = ws_stream.next() => {
|
||||||
|
match incoming {
|
||||||
|
Some(Ok(msg)) => println!("From server: {}", msg.as_text()?),
|
||||||
|
Some(Err(err)) => return Err(err.into()),
|
||||||
|
None => return Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = stdin.read_line(&mut line) => {
|
||||||
|
match res {
|
||||||
|
Ok(0) => return Ok(()),
|
||||||
|
Ok(_) => ws_stream.send(Message::text(line.trim_end().to_string())).await?,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
src/exercises/concurrency/chat-async/src/bin/server.rs
Normal file
82
src/exercises/concurrency/chat-async/src/bin/server.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use futures_util::sink::SinkExt;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
use tokio::sync::broadcast::error::{RecvError, SendError};
|
||||||
|
use tokio::sync::broadcast::{channel, Sender};
|
||||||
|
use tokio_websockets::{Message, ServerBuilder, WebsocketStream};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
enum ServerError {
|
||||||
|
#[error("websocket error: {0}")]
|
||||||
|
Websocket(String),
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[error("broadcast channel SendError: {0}")]
|
||||||
|
SendError(#[from] SendError<String>),
|
||||||
|
#[error("broadcast channel RecvError: {0}")]
|
||||||
|
RecvError(#[from] RecvError),
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokio_websockets Error types do not implement std::error::Error, so we make do by just capturing
|
||||||
|
// the debug format for the error.
|
||||||
|
impl From<tokio_websockets::Error> for ServerError {
|
||||||
|
fn from(err: tokio_websockets::Error) -> Self {
|
||||||
|
ServerError::Websocket(format!("{:?}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<tokio_websockets::proto::ProtocolError> for ServerError {
|
||||||
|
fn from(err: tokio_websockets::proto::ProtocolError) -> Self {
|
||||||
|
ServerError::Websocket(format!("{:?}", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_connection(
|
||||||
|
addr: SocketAddr,
|
||||||
|
mut ws_stream: WebsocketStream<TcpStream>,
|
||||||
|
bcast_tx: Sender<String>,
|
||||||
|
) -> Result<(), ServerError> {
|
||||||
|
ws_stream
|
||||||
|
.send(Message::text("Welcome to chat! Type a message".into()))
|
||||||
|
.await?;
|
||||||
|
let mut bcast_rx = bcast_tx.subscribe();
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
incoming = ws_stream.next() => {
|
||||||
|
match incoming {
|
||||||
|
Some(Ok(msg)) => {
|
||||||
|
let msg = msg.as_text()?;
|
||||||
|
println!("From client {addr:?} {msg:?}");
|
||||||
|
bcast_tx.send(msg.into())?;
|
||||||
|
}
|
||||||
|
Some(Err(err)) => return Err(err.into()),
|
||||||
|
None => return Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg = bcast_rx.recv() => {
|
||||||
|
ws_stream.send(Message::text(msg?)).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), ServerError> {
|
||||||
|
let (bcast_tx, _) = channel(16);
|
||||||
|
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:2000").await?;
|
||||||
|
println!("listening on port 2000");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (socket, addr) = listener.accept().await?;
|
||||||
|
println!("New connection from {addr:?}");
|
||||||
|
let bcast_tx = bcast_tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
// Wrap the raw TCP stream into a websocket.
|
||||||
|
let ws_stream = ServerBuilder::new().accept(socket).await?;
|
||||||
|
|
||||||
|
handle_connection(addr, ws_stream, bcast_tx).await
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user