--- /dev/null
+[package]
+name = "actix-server"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+actix = "0.13"
+actix-web = "4"
+actix-web-actors = "4.1"
+actix-files = "0.6"
+env_logger = "0.9"
+log = "0.4"
+
+[dev-dependencies]
+actix-test = "0.1"
+futures-util = "0.3"
--- /dev/null
+use std::path::Path;
+
+use actix::{Actor, ActorContext, StreamHandler};
+use actix_web::{get, route, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder, Result};
+use actix_web::http::header;
+use actix_web::middleware::Logger;
+use actix_files::{Files, NamedFile};
+use actix_web_actors::ws;
+
+#[route("/hello/{name}", method="GET", method="HEAD")]
+async fn hello(params: web::Path<String>, req: HttpRequest) -> Result<String> {
+ let name = params.into_inner();
+
+ match req.headers().get(header::USER_AGENT) {
+ Some(agent) => Ok(format!("Hello {} from {}!", name, agent.to_str().unwrap())),
+ None => Ok(format!("Hello {}!", name))
+ }
+}
+
+#[get("/file/{path:.*}")]
+async fn static_file(params: web::Path<String>, req: HttpRequest) -> Result<impl Responder> {
+ let request_path = params.into_inner();
+ let disk_path = "../static/".to_string() + &request_path;
+
+ // if the client accepts gzip encoding, try that first
+ if let Some(accept_encoding) = req.headers().get(header::ACCEPT_ENCODING) {
+ if accept_encoding.to_str().unwrap().contains("gzip") {
+ let path_gz = disk_path.clone() + ".gz";
+ if Path::new(&path_gz).is_file() {
+ log::debug!("client accepts gzip encoding, sending pre-compressed file {}", &path_gz);
+ return Ok(NamedFile::open_async(path_gz).await?
+ .customize()
+ .insert_header(header::ContentEncoding::Gzip));
+ }
+ }
+ }
+
+ // uncompressed file
+ Ok(NamedFile::open_async(disk_path).await?.customize())
+}
+
+struct WsEcho;
+
+impl Actor for WsEcho {
+ type Context = ws::WebsocketContext<Self>;
+}
+
+impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsEcho {
+ fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
+ log::info!("WsEcho got message {:?}", msg);
+ match msg {
+ Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
+ Ok(ws::Message::Text(text)) => ctx.text(text),
+ Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
+ Ok(ws::Message::Close(reason)) => {
+ ctx.close(reason);
+ ctx.stop();
+ },
+ _ => ctx.stop(),
+ }
+ }
+}
+
+#[get("/ws-echo")]
+async fn ws_echo(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
+ ws::start(WsEcho {}, &req, stream)
+}
+
+struct WsRev;
+
+impl Actor for WsRev {
+ type Context = ws::WebsocketContext<Self>;
+}
+
+impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsRev {
+ fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
+ log::info!("WsRev got message {:?}", msg);
+ match msg {
+ Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
+ Ok(ws::Message::Text(text)) => {
+ let rev = text.chars().rev().collect::<String>();
+ ctx.text(rev);
+ },
+ Ok(ws::Message::Binary(bin)) => {
+ let mut rev = bin.to_vec();
+ rev.reverse();
+ ctx.binary(rev);
+ }
+ Ok(ws::Message::Close(reason)) => {
+ ctx.close(reason);
+ ctx.stop();
+ },
+ _ => ctx.stop(),
+ }
+ }
+}
+
+#[get("/ws-rev")]
+async fn ws_rev(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
+ ws::start(WsRev {}, &req, stream)
+}
+
+// App is a template soup, too hard as a proper function
+macro_rules! get_app {
+ () => {
+ App::new()
+ .service(hello)
+ .service(static_file)
+ .service(Files::new("/dir", "../static"))
+ .service(ws_echo)
+ .service(ws_rev)
+ }
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
+
+ HttpServer::new(|| {
+ get_app!()
+ .wrap(Logger::default())
+ })
+ .bind(("127.0.0.1", 3030))?
+ .run()
+ .await
+}
+
+#[cfg(test)]
+mod tests {
+ use actix_web::{App, body, test, web};
+ use actix_web::http::{header, StatusCode};
+ use actix_web_actors::ws;
+ use actix_files::Files;
+
+ use futures_util::sink::SinkExt;
+ use futures_util::StreamExt;
+
+ use super::{hello, static_file, ws_echo, ws_rev};
+
+ #[actix_web::test]
+ async fn test_hello() {
+ let app = test::init_service(get_app!()).await;
+
+ // no user-agent
+ let req = test::TestRequest::get().uri("/hello/rust").to_request();
+ let res = test::call_service(&app, req).await;
+ assert!(res.status().is_success());
+ assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
+ web::Bytes::from_static(b"Hello rust!"));
+
+ // with user-agent
+ let req = test::TestRequest::get()
+ .uri("/hello/rust")
+ .insert_header((header::USER_AGENT, "TestBrowser 0.1"))
+ .to_request();
+ let res = test::call_service(&app, req).await;
+ assert!(res.status().is_success());
+ assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
+ web::Bytes::from_static(b"Hello rust from TestBrowser 0.1!"));
+ }
+
+ #[actix_web::test]
+ async fn test_static_dir() {
+ let app = test::init_service(get_app!()).await;
+
+ let req = test::TestRequest::get().uri("/dir/plain.txt").to_request();
+ let res = test::call_service(&app, req).await;
+ assert!(res.status().is_success());
+ assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
+ web::Bytes::from_static(b"Hello world! This is uncompressed text.\n"));
+
+ // subdir
+ let req = test::TestRequest::get().uri("/dir/dir1/optzip.txt").to_request();
+ let res = test::call_service(&app, req).await;
+ assert!(res.status().is_success());
+ assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
+ web::Bytes::from_static(b"This file is available uncompressed or compressed\n\
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"));
+
+ // does not support transparent decompression
+ let req = test::TestRequest::get().uri("/dir/onlycompressed.txt").to_request();
+ let res = test::call_service(&app, req).await;
+ assert_eq!(res.status(), StatusCode::NOT_FOUND);
+ }
+
+ #[actix_web::test]
+ async fn test_static_file() {
+ let app = test::init_service(get_app!()).await;
+
+ // uncompressed
+ let req = test::TestRequest::get().uri("/file/dir1/optzip.txt").to_request();
+ let res = test::call_service(&app, req).await;
+ assert!(res.status().is_success());
+ assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
+ web::Bytes::from_static(b"This file is available uncompressed or compressed\n\
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"));
+
+ // gzipped
+ let req = test::TestRequest::get()
+ .uri("/file/dir1/optzip.txt")
+ .insert_header((header::ACCEPT_ENCODING, "deflate, gzip"))
+ .to_request();
+ let res = test::call_service(&app, req).await;
+ assert!(res.status().is_success());
+ let res_bytes = body::to_bytes(res.into_body()).await.unwrap();
+ assert_eq!(res_bytes.len(), 63); // file size of ../static/dir1/optzip.txt.gz
+ assert_eq!(res_bytes[0], 31);
+ }
+
+ #[actix_web::test]
+ async fn test_ws_echo() {
+ let mut srv = actix_test::start(|| get_app!());
+ let mut client = srv.ws_at("/ws-echo").await.unwrap();
+
+ // text echo
+ client.send(ws::Message::Text("hello".into())).await.unwrap();
+ let received = client.next().await.unwrap().unwrap();
+ assert_eq!(received, ws::Frame::Text("hello".into()));
+
+ // binary echo
+ client.send(ws::Message::Binary(web::Bytes::from_static(&[42, 99]))).await.unwrap();
+ let received = client.next().await.unwrap().unwrap();
+ assert_eq!(received, ws::Frame::Binary(web::Bytes::from_static(&[42, 99])));
+ }
+
+ #[actix_web::test]
+ async fn test_ws_rev() {
+ let mut srv = actix_test::start(|| get_app!());
+ let mut client = srv.ws_at("/ws-rev").await.unwrap();
+
+ // text reversed
+ client.send(ws::Message::Text("hello".into())).await.unwrap();
+ let received = client.next().await.unwrap().unwrap();
+ assert_eq!(received, ws::Frame::Text("olleh".into()));
+
+ // binary reversed
+ client.send(ws::Message::Binary(web::Bytes::from_static(&[42, 99]))).await.unwrap();
+ let received = client.next().await.unwrap().unwrap();
+ assert_eq!(received, ws::Frame::Binary(web::Bytes::from_static(&[99, 42])));
+ }
+}
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Hello!</title>
+ </head>
+ <body>
+ <h1>Oops!</h1>
+ <p>I don't know about this resource.</p>
+ </body>
+</html>
--- /dev/null
+[package]
+name = "async-http"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+futures = "0.3"
+log = "0.4"
+env_logger = "0.9"
+
+[dependencies.async-std]
+version = "1.6"
+features = ["attributes"]
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Hello!</title>
+ </head>
+ <body>
+ <h1>Hello!</h1>
+ <p>Hi from Rust</p>
+ </body>
+</html>
--- /dev/null
+use std::fs;
+use std::time::Duration;
+
+use async_std::prelude::*;
+use async_std::io::{ Read, Write };
+use async_std::net::{ TcpListener };
+use async_std::task;
+use futures::stream::StreamExt;
+
+#[async_std::main]
+async fn main() {
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
+
+ // Listen for incoming TCP connections on localhost port 7878
+ let listener = TcpListener::bind("127.0.0.1:7878").await.unwrap();
+
+ listener.incoming().for_each_concurrent(/* limit */ None, |tcpstream| async move {
+ let tcpstream = tcpstream.unwrap();
+ task::spawn(handle_connection(tcpstream));
+ }).await;
+}
+
+async fn handle_connection(mut stream: impl Read + Write + Unpin) {
+ // Read the first 1024 bytes of data from the stream
+ let mut buffer = [0; 1024];
+ assert!(stream.read(&mut buffer).await.unwrap() > 0);
+
+ // Respond with greetings or a 404,
+ // depending on the data in the request
+ let (status_line, filename) = if buffer.starts_with(b"GET / HTTP/1.1\r\n") {
+ ("HTTP/1.1 200 OK", "index.html")
+ } else if buffer.starts_with(b"GET /sleep HTTP/1.1\r\n") {
+ task::sleep(Duration::from_secs(5)).await;
+ // sync version, to demonstrate concurrent async vs. parallel threads
+ // std::thread::sleep(Duration::from_secs(5));
+ ("HTTP/1.1 201 Sleep", "index.html")
+ } else {
+ ("HTTP/1.1 404 NOT FOUND", "404.html")
+ };
+ let contents = fs::read_to_string(filename).unwrap();
+ log::info!("GET {} {}", filename, status_line);
+
+ // Write response back to the stream,
+ // and flush the stream to ensure the response is sent back to the client
+ let response = format!("{status_line}\r\n\r\n{contents}");
+ stream.write_all(response.as_bytes()).await.unwrap();
+ stream.flush().await.unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::cmp;
+ use std::pin::Pin;
+
+ use futures::io::Error;
+ use futures::task::{Context, Poll};
+
+ struct MockTcpStream {
+ read_data: Vec<u8>,
+ write_data: Vec<u8>,
+ }
+
+ impl Read for MockTcpStream {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ _: &mut Context,
+ buf: &mut [u8],
+ ) -> Poll<Result<usize, Error>> {
+ let size: usize = cmp::min(self.read_data.len(), buf.len());
+ buf[..size].copy_from_slice(&self.read_data[..size]);
+ Poll::Ready(Ok(size))
+ }
+ }
+
+ impl Write for MockTcpStream {
+ fn poll_write(
+ mut self: Pin<&mut Self>,
+ _: &mut Context,
+ buf: &[u8],
+ ) -> Poll<Result<usize, Error>> {
+ self.write_data = Vec::from(buf);
+
+ Poll::Ready(Ok(buf.len()))
+ }
+
+ fn poll_flush(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn poll_close(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Error>> {
+ Poll::Ready(Ok(()))
+ }
+ }
+
+ impl Unpin for MockTcpStream {}
+
+ #[async_std::test]
+ async fn test_handle_connection() {
+ let input_bytes = b"GET / HTTP/1.1\r\n";
+ let mut contents = vec![0u8; 1024];
+ contents[..input_bytes.len()].clone_from_slice(input_bytes);
+ let mut stream = MockTcpStream {
+ read_data: contents,
+ write_data: Vec::new(),
+ };
+
+ handle_connection(&mut stream).await;
+
+ let expected_contents = fs::read_to_string("index.html").unwrap();
+ let expected_response = format!("HTTP/1.1 200 OK\r\n\r\n{}", expected_contents);
+ assert!(stream.write_data.starts_with(expected_response.as_bytes()));
+ }
+}
--- /dev/null
+[package]
+name = "axum-server"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+axum = { version = "0.5", features = ["ws", "headers"] }
+hyper = { version = "0.14" }
+tokio = { version = "1", features = ["full"] }
+tower = "0.4"
+tower-http = { version = "0.3", features = ["trace", "fs"] }
+tracing = "0.1"
+tracing-subscriber = "0.3"
--- /dev/null
+use std::io;
+
+use axum::{
+ routing::{get, get_service},
+ extract::{Path, TypedHeader, ws},
+ http::{StatusCode},
+ response,
+ Router};
+
+async fn hello(Path(name): Path<String>, user_agent: Option<TypedHeader<axum::headers::UserAgent>>) -> impl response::IntoResponse {
+ if let Some(TypedHeader(user_agent)) = user_agent {
+ (StatusCode::OK, format!("Hello {} from {}", name, user_agent))
+ } else {
+ (StatusCode::OK, format!("Hello {}", name))
+ }
+}
+
+async fn ws_echo(mut socket: ws::WebSocket) {
+ while let Some(msg) = socket.recv().await {
+ if let Ok(msg) = msg {
+ tracing::debug!("websocket got message: {:?}", msg);
+
+ let reply = match msg {
+ ws::Message::Text(t) => ws::Message::Text(t),
+ ws::Message::Binary(b) => ws::Message::Binary(b),
+ // axum handles Ping/Pong by itself
+ ws::Message::Ping(_) => { continue },
+ ws::Message::Pong(_) => { continue },
+ ws::Message::Close(_) => { break }
+ };
+
+ if socket.send(reply).await
+ .is_err() {
+ tracing::info!("websocket client disconnected");
+ break;
+ }
+ }
+ else {
+ tracing::info!("websocket client disconnected");
+ break;
+ }
+ }
+}
+
+fn app() -> Router {
+ Router::new()
+ .route("/hello/:name", get(hello))
+ .nest("/dir",
+ get_service(tower_http::services::ServeDir::new("../static").precompressed_gzip())
+ .handle_error(|e: io::Error| async move {
+ (StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {}", e))
+ })
+ )
+ .route("/ws-echo", get(|ws: ws::WebSocketUpgrade| async {ws.on_upgrade(ws_echo)}))
+ .layer(
+ tower::ServiceBuilder::new()
+ .layer(
+ tower_http::trace::TraceLayer::new_for_http()
+ .make_span_with(tower_http::trace::DefaultMakeSpan::default().include_headers(true)),
+ )
+ )
+}
+
+#[tokio::main]
+async fn main() {
+ tracing_subscriber::fmt::init();
+
+ let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3030));
+ tracing::info!("listening on {}", addr);
+ axum::Server::bind(&addr)
+ .serve(app().into_make_service())
+ .await
+ .unwrap();
+}
+
+#[cfg(test)]
+mod tests {
+ use axum::{
+ http::{Request, StatusCode},
+ response::Response,
+ body::Body
+ };
+ use tower::ServiceExt; // for `oneshot`
+
+ async fn assert_res_ok_body(res: Response, expected_body: &[u8]) {
+ assert_eq!(res.status(), StatusCode::OK);
+ assert_eq!(hyper::body::to_bytes(res.into_body()).await.unwrap(), expected_body);
+ }
+
+ #[tokio::test]
+ async fn test_hello() {
+ // no user-agent
+ let res = super::app()
+ .oneshot(Request::builder().uri("/hello/rust").body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+ assert_res_ok_body(res, b"Hello rust").await;
+
+ // with user-agent
+ let res = super::app()
+ .oneshot(Request::builder()
+ .uri("/hello/rust")
+ .header("user-agent", "TestBrowser 0.1")
+ .body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+ assert_res_ok_body(res, b"Hello rust from TestBrowser 0.1").await;
+ }
+
+ #[tokio::test]
+ async fn test_static_dir() {
+ let res = super::app()
+ .oneshot(Request::builder().uri("/dir/plain.txt").body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+ assert_res_ok_body(res, b"Hello world! This is uncompressed text.\n").await;
+
+ // transparent .gz lookup, without gzip transfer encoding
+ let res = super::app()
+ .oneshot(Request::builder()
+ .uri("/dir/dir1/optzip.txt")
+ .header("accept-encoding", "deflate")
+ .body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
+ // that returns the uncompressed file
+ assert_res_ok_body(res, b"This file is available uncompressed or compressed\n\
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n").await;
+
+ // transparent .gz lookup, with gzip transfer encoding
+ let res = super::app()
+ .oneshot(Request::builder()
+ .uri("/dir/dir1/optzip.txt")
+ .header("accept-encoding", "deflate, gzip")
+ .body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
+ let res_bytes: &[u8] = &hyper::body::to_bytes(res.into_body()).await.unwrap();
+ // that returns the compressed file
+ assert_eq!(res_bytes.len(), 63); // file size of ../static/dir1/optzip.txt.gz
+ assert_eq!(res_bytes[0], 31);
+ }
+}
+use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
-use std::collections::HashMap;
pub fn read_file(path: &str) -> Result<String, std::io::Error> {
let mut s = String::new();
- File::open(path)?
- .read_to_string(&mut s)?;
+ File::open(path)?.read_to_string(&mut s)?;
Ok(s)
}
V: Copy,
{
pub fn new(calc: T) -> Cacher<T, A, V> {
- Cacher { calc, values: HashMap::new() }
+ Cacher {
+ calc,
+ values: HashMap::new(),
+ }
}
pub fn value(&mut self, arg: A) -> V {
}
pub struct Counter5 {
- count: u32
+ count: u32,
}
impl Counter5 {
struct Draft {}
impl State for Draft {
fn request_review(&self) -> Box<dyn State> {
- Box::new(PendingReview {acks: 0})
+ Box::new(PendingReview { acks: 0 })
}
fn approve(&mut self) -> Box<dyn State> {
impl State for PendingReview {
fn request_review(&self) -> Box<dyn State> {
- Box::new(Self {acks: self.acks})
+ Box::new(Self { acks: self.acks })
}
fn approve(&mut self) -> Box<dyn State> {
if self.acks >= 1 {
Box::new(Published {})
} else {
- Box::new(Self {acks: self.acks + 1})
+ Box::new(Self {
+ acks: self.acks + 1,
+ })
}
}
impl TPost {
pub fn new() -> TPostDraft {
- TPostDraft {content: String::new()}
+ TPostDraft {
+ content: String::new(),
+ }
}
pub fn content(&self) -> &str {
}
pub fn request_review(self) -> TPostReview {
- TPostReview {content: self.content}
+ TPostReview {
+ content: self.content,
+ }
}
}
impl TPostReview {
pub fn approve(self) -> TPost {
- TPost {content: self.content}
+ TPost {
+ content: self.content,
+ }
}
pub fn reject(self) -> TPostDraft {
- TPostDraft {content: self.content}
+ TPostDraft {
+ content: self.content,
+ }
}
}
-mod word_utils;
mod lib;
+mod word_utils;
use std::collections::HashMap;
-use std::io::{prelude::*, ErrorKind};
use std::fs::{self, File};
-use std::{thread, time, sync};
+use std::io::{prelude::*, ErrorKind};
+use std::{sync, thread, time};
use lib::*;
use word_utils::{first_word, second_word};
println!("second word: '{}'", second_word(&s).unwrap());
let s2 = "hello dude blah";
- println!("second word of single: '{}'", second_word(s2).unwrap_or("(none)"));
+ println!(
+ "second word of single: '{}'",
+ second_word(s2).unwrap_or("(none)")
+ );
match second_word(s2) {
Some(w) => println!("match: second word of '{}' exists: {}", s2, w),
println!("scores after doubling: {:?}", scores);
// double scores of immutable hashmap (rebuild it)
- let collect_scores: HashMap<_, _> = collect_scores.into_iter()
+ let collect_scores: HashMap<_, _> = collect_scores
+ .into_iter()
.map(|(k, v)| (k, 2 * v))
.collect();
- println!("collect_scores after rebuilding with doubling: {:?}", collect_scores);
+ println!(
+ "collect_scores after rebuilding with doubling: {:?}",
+ collect_scores
+ );
}
fn test_files() {
if let Ok(mut f) = File::open("Cargo.toml") {
let mut contents = String::new();
match f.read_to_string(&mut contents) {
- Ok(len) => println!("successfully opened Cargo.toml: {:?}, contents {} bytes:\n{}\n----------", f, len, contents),
- Err(e) => panic!("could not read file: {:?}", e)
+ Ok(len) => println!(
+ "successfully opened Cargo.toml: {:?}, contents {} bytes:\n{}\n----------",
+ f, len, contents
+ ),
+ Err(e) => panic!("could not read file: {:?}", e),
}
} else {
println!("could not open Cargo.toml");
// using the '?' operator
match read_file("Cargo.toml") {
Ok(s) => println!("Cargo.toml contents:\n{}\n-------------", s),
- Err(e) => println!("Could not open Cargo.toml: {:?}", e)
+ Err(e) => println!("Could not open Cargo.toml: {:?}", e),
}
// using std API
match fs::read_to_string("Cargo.toml") {
Ok(s) => println!("Cargo.toml contents:\n{}\n-------------", s),
- Err(e) => println!("Could not open Cargo.toml: {:?}", e)
+ Err(e) => println!("Could not open Cargo.toml: {:?}", e),
}
}
println!("str_list: {:?}", str_list);
let string_list = vec!["aaaa".to_string(), "xxxxx".to_string(), "ffff".to_string()];
- println!("largest string (with cloning): {}", largest_clone(&string_list));
+ println!(
+ "largest string (with cloning): {}",
+ largest_clone(&string_list)
+ );
println!("largest string (with ref): {}", largest_ref(&string_list));
println!("string_list: {:?}", string_list);
2 * x
});
- println!("1st int call for value 1: {}", expensive_int_result.value(1));
- println!("2nd int call for value 1: {}", expensive_int_result.value(1));
- println!("1st int call for value 2: {}", expensive_int_result.value(2));
+ println!(
+ "1st int call for value 1: {}",
+ expensive_int_result.value(1)
+ );
+ println!(
+ "2nd int call for value 1: {}",
+ expensive_int_result.value(1)
+ );
+ println!(
+ "1st int call for value 2: {}",
+ expensive_int_result.value(2)
+ );
let mut expensive_str_result = Cacher::new(|x: &str| {
println!("calculating expensive str result for {}", x);
x.len()
});
- println!("1st int call for value abc: {}", expensive_str_result.value("abc"));
- println!("2nd int call for value abc: {}", expensive_str_result.value("abc"));
- println!("1st int call for value defg: {}", expensive_str_result.value("defg"));
+ println!(
+ "1st int call for value abc: {}",
+ expensive_str_result.value("abc")
+ );
+ println!(
+ "2nd int call for value abc: {}",
+ expensive_str_result.value("abc")
+ );
+ println!(
+ "1st int call for value defg: {}",
+ expensive_str_result.value("defg")
+ );
}
fn test_iterators() {
post.request_review();
assert_eq!("", post.content());
- post.approve(); // first
+ post.approve(); // first
assert_eq!("", post.content());
- post.approve(); // second
+ post.approve(); // second
assert_eq!(text, post.content());
- post.reject(); // no-op
+ post.reject(); // no-op
assert_eq!(text, post.content());
}
--- /dev/null
+[package]
+name = "gtk3-hello-world"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+gtk = "0.15"
--- /dev/null
+use gtk::prelude::*;
+use gtk::{
+ Application, ApplicationWindow,
+ Button,
+ Widget,
+};
+
+fn build_ui(app: &Application) {
+ let button = Button::builder()
+ .label("Click me!")
+ .margin_top(12)
+ .margin_bottom(12)
+ .margin_start(12)
+ .margin_end(12)
+ .build();
+
+ button.connect_clicked(move |button| {
+ button.set_label("Hello world!");
+ });
+
+ let button_w: &Widget = button.upcast_ref::<Widget>();
+
+ println!("button visible: {}", button_w.is_visible());
+
+ let window = ApplicationWindow::builder()
+ .application(app)
+ .title("Hello GTK")
+ .child(&button)
+ .build();
+
+ window.show_all();
+}
+
+fn main() {
+ let app = Application::builder()
+ .application_id("ork.gtk-rs.example")
+ .build();
+
+ app.connect_activate(build_ui);
+ app.run();
+}
--- /dev/null
+[package]
+name = "gtk4-hello-world"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+gtk = { version = "0.4", package = "gtk4" }
--- /dev/null
+use gtk::prelude::*;
+use gtk::{
+ Application, ApplicationWindow,
+ Button
+};
+
+fn build_ui(app: &Application) {
+ let button = Button::builder()
+ .label("Click me!")
+ .margin_top(12)
+ .margin_bottom(12)
+ .margin_start(12)
+ .margin_end(12)
+ .build();
+
+ button.connect_clicked(|button| {
+ button.set_label("Hello world!");
+ });
+
+ let window = ApplicationWindow::builder()
+ .application(app)
+ .title("Hello GTK")
+ .child(&button)
+ .build();
+
+ window.present();
+}
+
+fn main() {
+ let app = Application::builder()
+ .application_id("ork.gtk-rs.example")
+ .build();
+
+ app.connect_activate(build_ui);
+ app.run();
+}
--- /dev/null
+[package]
+name = "hyper-server"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+futures = "0.3"
+hyper = { version = "0.14", features = ["server", "runtime", "http1"] }
+tokio = { version = "1", features = ["full"] }
--- /dev/null
+use std::convert::Infallible;
+use std::net::SocketAddr;
+
+use hyper::service::{make_service_fn, service_fn};
+use hyper::{Body, Request, Response, Server, Method, StatusCode};
+
+async fn service(req: Request<Body>) -> Result<Response<Body>, Infallible> {
+ let mut response = Response::new(Body::empty());
+
+ match (req.method(), req.uri().path()) {
+ (&Method::GET, "/") => {
+ *response.body_mut() = Body::from("Try POSTing data to /echo");
+ },
+ (&Method::POST, "/echo") => {
+ *response.body_mut() = req.into_body();
+ },
+ _ => {
+ *response.status_mut() = StatusCode::NOT_FOUND;
+ }
+ }
+
+ Ok(response)
+}
+
+#[tokio::main]
+async fn main() {
+ // We'll bind to 127.0.0.1:3000
+ let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
+ let make_svc = make_service_fn(|_conn| async {
+ Ok::<_, Infallible>(service_fn(service))
+ });
+ let server = Server::bind(&addr).serve(make_svc);
+ let graceful = server.with_graceful_shutdown(async {
+ tokio::signal::ctrl_c()
+ .await
+ .expect("failed to install CTRL+C signal handler");
+ });
+
+ if let Err(e) = graceful.await {
+ eprintln!("server error: {}", e);
+ }
+}
}
fn main() -> Result<(), Box<dyn Error>> {
- env_logger::init();
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
create_contacts()?;
let contacts = load_contacts()?;
- println!("deserialized: {:?}", &contacts);
+ log::info!("deserialized: {:?}", &contacts);
Ok(())
}
--- /dev/null
+This directory contains static files for serving through HTTP frameworks.
--- /dev/null
+This file is available uncompressed or compressed
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
--- /dev/null
+Hello world! This is uncompressed text.
--- /dev/null
+[package]
+name = "tokio-tutorial-jbarszczewski"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+tokio = {version = "1.21", features = ["full"]}
--- /dev/null
+use std::str;
+use std::sync::{ Arc, Mutex };
+
+use tokio::io::{ AsyncReadExt, AsyncWriteExt };
+use tokio::net::{ TcpListener, TcpStream };
+
+#[tokio::main]
+async fn main() {
+ let balance = Arc::new(Mutex::new(0.0f64));
+ let listener = TcpListener::bind("127.0.0.1:8181").await.unwrap();
+
+ loop {
+ let (stream, _) = listener.accept().await.unwrap();
+ let balance_ref = balance.clone();
+ tokio::spawn(async move { handle_connection(stream, balance_ref).await });
+ }
+}
+
+async fn handle_connection(mut stream: TcpStream, balance: Arc<Mutex<f64>>) {
+ // Read the first 16 characters from the incoming stream
+ let mut buffer = [0; 16];
+ assert!(stream.read(&mut buffer).await.unwrap() >= 16);
+ // First 4 characters are used to detect HTTP method
+ let method_type = match str::from_utf8(&buffer[0..4]) {
+ Ok(v) => v,
+ Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
+ };
+
+ let contents = match method_type {
+ "GET " => {
+ format!("{{\"balance\": {}}}", balance.lock().unwrap())
+ }
+ "POST" => {
+ // Take characters after 'POST /' until whitespace is detected.
+ let input: String = buffer[6..16]
+ .iter()
+ .take_while(|x| **x != 32u8)
+ .map(|x| *x as char)
+ .collect();
+ let balance_update = input.parse::<f64>().unwrap();
+ println!("got POST request to update by {}", balance_update);
+ let mut locked_balance = balance.lock().unwrap();
+ *locked_balance += balance_update;
+ format!("{{\"balance\": {}}}", locked_balance)
+ }
+ _ => {
+ panic!("Invalid HTTP method!")
+ }
+ };
+
+ let response = format!(
+ "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}",
+ contents.len(),
+ contents
+ );
+ assert!(stream.write(response.as_bytes()).await.unwrap() > 0);
+ stream.flush().await.unwrap();
+}
--- /dev/null
+[package]
+name = "tokio-tutorial-mini-redis"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+bytes = "1"
+env_logger = "0.9"
+log = "0.4"
+mini-redis = "0.4"
+tokio = { version = "1", features = ["full"] }
--- /dev/null
+use mini_redis::{client, Result};
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // Open a connection to the mini-redis address.
+ let mut client = client::connect("127.0.0.1:6379").await?;
+
+ // Set the key "hello" with value "world"
+ client.set("hello", "world".into()).await?;
+
+ // Get key "hello"
+ let result = client.get("hello").await?;
+
+ println!("got value from the server; result={:?}", result);
+
+ Ok(())
+}
--- /dev/null
+use bytes::Bytes;
+use mini_redis::client;
+use tokio::sync::{ mpsc, oneshot };
+
+/// Provided by the requester and used by the manager task to send
+/// the command response back to the requester.
+type Responder<T> = oneshot::Sender<mini_redis::Result<T>>;
+
+#[derive(Debug)]
+enum Command {
+ Get {
+ key: String,
+ resp: Responder<Option<Bytes>>,
+ },
+ Set {
+ key: String,
+ val: Bytes,
+ resp: Responder<()>,
+ }
+}
+
+#[tokio::main]
+async fn main() {
+ let (manager_tx, mut manager_rx) = mpsc::channel(32);
+
+ let manager = tokio::spawn(async move {
+ // Establish a connection to the server
+ let mut client = client::connect("127.0.0.1:6379").await.unwrap();
+
+ while let Some(cmd) = manager_rx.recv().await {
+ match cmd {
+ Command::Get { key, resp } => { resp.send(client.get(&key).await).unwrap(); }
+ Command::Set { key, val, resp } => { resp.send(client.set(&key, val).await).unwrap(); }
+ }
+ }
+ });
+
+ let manager_tx2 = manager_tx.clone();
+ // Spawn two tasks, one gets a key, the other sets a key
+ let t1 = tokio::spawn(async move {
+ let (resp_tx, resp_rx) = oneshot::channel();
+ manager_tx.send(Command::Get { key: "hello".to_string(), resp: resp_tx }).await.unwrap();
+ let res = resp_rx.await;
+ println!("t1: got {:?}", res);
+ });
+
+ let t2 = tokio::spawn(async move {
+ let (resp_tx, resp_rx) = oneshot::channel();
+ manager_tx2.send(Command::Set { key: "hello".to_string(), val: "bar".into(), resp: resp_tx }).await.unwrap();
+ let res = resp_rx.await.unwrap();
+ println!("t2: got {:?}", res);
+ });
+
+ t1.await.unwrap();
+ t2.await.unwrap();
+ manager.await.unwrap();
+}
--- /dev/null
+use std::collections::HashMap;
+use std::error::Error;
+use std::sync::{Arc, Mutex};
+
+use bytes::Bytes;
+use mini_redis::{Connection, Frame};
+use mini_redis::Command::{self, Get, Set};
+use tokio::net::{TcpListener, TcpStream};
+
+type Db = Arc<Mutex<HashMap<String, Bytes>>>;
+
+const LISTEN: &str = "127.0.0.1:6379";
+
+#[tokio::main]
+async fn main() {
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug")).init();
+
+ let listener = TcpListener::bind(LISTEN).await.unwrap();
+ log::info!("Listening on {}", LISTEN);
+ let db: Db = Arc::new(Mutex::new(HashMap::new()));
+
+ loop {
+ match listener.accept().await {
+ Ok((socket, addr)) => {
+ log::debug!("got connection from {:?}", addr);
+ let db_i = db.clone();
+ tokio::spawn(async move {
+ if let Err(e) = process(socket, db_i).await {
+ log::warn!("failed: {:?}", e);
+ }
+ });
+ },
+ Err(e) => log::warn!("Failed to accept connection: {}", e),
+ };
+ }
+}
+
+async fn process(socket: TcpStream, db: Db) -> Result<(), Box<dyn Error + Send + Sync>> {
+ let mut connection = Connection::new(socket);
+
+ while let Some(frame) = connection.read_frame().await? {
+ let response = match Command::from_frame(frame)? {
+ Set(cmd) => {
+ // The value is stored as `Vec<u8>`
+ db.lock().unwrap().insert(cmd.key().to_string(), cmd.value().clone());
+ log::debug!("Set {} → {:?}", &cmd.key(), &cmd.value());
+ Frame::Simple("OK".to_string())
+ }
+ Get(cmd) => {
+ if let Some(value) = db.lock().unwrap().get(cmd.key()) {
+ log::debug!("Get {} → {:?}", &cmd.key(), &value);
+ Frame::Bulk(value.clone())
+ } else {
+ log::debug!("Get {} unknown key", &cmd.key());
+ Frame::Null
+ }
+ }
+ cmd => panic!("unimplemented {:?}", cmd),
+ };
+
+ // Write the response to the client
+ connection.write_frame(&response).await?;
+ }
+ Ok(())
+}
--- /dev/null
+[package]
+name = "warp-server"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+futures-util = { version = "0.3", default-features = false, features = ["sink"] }
+tokio = { version = "1", features = ["full"] }
+env_logger = "0.9"
+log = "0.4"
+warp = "0.3"
--- /dev/null
+use futures_util::{FutureExt, StreamExt, SinkExt};
+use warp::Filter;
+
+// GET /hello/warp => 200 OK with body "Hello, warp!"
+async fn hello(name: String, agent: String) -> Result<impl warp::Reply, warp::Rejection> {
+ Ok(format!("Hello, {} from {}!", name, agent))
+}
+
+// websocat ws://127.0.0.1:3030/ws-echo
+async fn ws_echo_connected(websocket: warp::ws::WebSocket) {
+ // echo all messages back
+ let (tx, rx) = websocket.split();
+ rx.forward(tx).map(|result| {
+ if let Err(e) = result {
+ log::warn!("websocket error: {:?}", e);
+ }
+ }).await;
+}
+
+// websocat ws://127.0.0.1:3030/ws-rev
+async fn ws_rev_connected(websocket: warp::ws::WebSocket) {
+ // echo all messages back
+ tokio::task::spawn(async {
+ let (mut tx, mut rx) = websocket.split();
+ while let Some(message) = rx.next().await {
+ let msg = match message {
+ Ok(msg) => msg,
+ Err(e) => {
+ log::error!("websocket error: {}", e);
+ break;
+ }
+ };
+ log::info!("ws_rev_connected: got message: {:?}", msg);
+
+ if msg.is_close() {
+ break;
+ }
+ if msg.is_text() {
+ let text = msg.to_str().unwrap();
+ let rev = text.chars().rev().collect::<String>();
+ if let Err(e) = tx.send(warp::ws::Message::text(rev)).await {
+ // disconnected
+ log::info!("peer disconnected: {}", e);
+ break;
+ }
+ }
+ if msg.is_binary() {
+ let mut rev = msg.into_bytes();
+ rev.reverse();
+ if let Err(e) = tx.send(warp::ws::Message::binary(rev)).await {
+ // disconnected
+ log::info!("peer disconnected: {}", e);
+ break;
+ }
+ }
+ }
+ log::info!("ws_rev_connected ended");
+ });
+}
+
+pub fn api() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ let hello = warp::path!("hello" / String)
+ .and(warp::header::<String>("user-agent"))
+ .and_then(hello);
+
+ let ws_echo = warp::path("ws-echo")
+ .and(warp::ws())
+ .map(|ws: warp::ws::Ws| { ws.on_upgrade(ws_echo_connected) });
+
+ let ws_rev = warp::path("ws-rev")
+ .and(warp::ws())
+ .map(|ws: warp::ws::Ws| { ws.on_upgrade(ws_rev_connected) });
+
+ let static_dir = warp::path("dir")
+ .and(warp::fs::dir("../static"));
+
+ hello
+ .or(ws_echo)
+ .or(ws_rev)
+ .or(static_dir)
+ .with(warp::log("warp-server"))
+}
+
+#[tokio::main]
+async fn main() {
+ env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
+
+ warp::serve(api())
+ .run(([127, 0, 0, 1], 3030))
+ .await;
+}
+
+#[cfg(test)]
+mod tests {
+ #[tokio::test]
+ async fn test_hello() {
+ let res = warp::test::request()
+ .path("/hello/rust")
+ .header("user-agent", "TestBrowser 0.1")
+ .reply(&super::api())
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(res.body(), "Hello, rust from TestBrowser 0.1!");
+ }
+
+ #[tokio::test]
+ async fn test_ws_echo() {
+ let mut client = warp::test::ws()
+ .path("/ws-echo")
+ .handshake(super::api())
+ .await
+ .expect("handshake failed");
+
+ // text
+ client.send_text("Hello").await;
+ let reply = client.recv().await.unwrap();
+ assert_eq!(reply.to_str().unwrap(), "Hello");
+
+ // binary
+ let msg: Vec<u8> = vec![42, 99];
+ client.send(warp::ws::Message::binary(msg.clone())).await;
+ let reply = client.recv().await.unwrap();
+ assert_eq!(reply.as_bytes(), &msg);
+
+ //close
+ client.send(warp::ws::Message::close()).await;
+ client.recv_closed().await.unwrap();
+ }
+
+ #[tokio::test]
+ async fn test_ws_rev() {
+ let mut client = warp::test::ws()
+ .path("/ws-rev")
+ .handshake(super::api())
+ .await
+ .expect("handshake failed");
+
+ // text
+ client.send_text("Hello\n").await;
+ let reply = client.recv().await.unwrap();
+ assert_eq!(reply.to_str().unwrap(), "\nolleH");
+
+ // binary
+ let msg: Vec<u8> = vec![42, 99];
+ client.send(warp::ws::Message::binary(msg)).await;
+ let reply = client.recv().await.unwrap();
+ assert_eq!(reply.as_bytes(), vec![99, 42]);
+
+ //close
+ client.send(warp::ws::Message::close()).await;
+ client.recv_closed().await.unwrap();
+ }
+
+ #[tokio::test]
+ async fn test_static_dir() {
+ let res = warp::test::request()
+ .path("/dir/plain.txt")
+ .reply(&super::api())
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(res.body(), "Hello world! This is uncompressed text.\n");
+
+ // subdir
+ let res = warp::test::request()
+ .path("/dir/dir1/optzip.txt")
+ .reply(&super::api())
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(res.body(), "This file is available uncompressed or compressed\n\
+ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n");
+
+ // fs::dir does not support transparent decompression
+ let res = warp::test::request()
+ .path("/dir/onlycompressed.txt")
+ .reply(&super::api())
+ .await;
+ assert_eq!(res.status(), 404);
+ }
+}