hyper-server: Initial version master
authorMartin Pitt <martin@piware.de>
Sun, 18 Sep 2022 18:28:53 +0000 (20:28 +0200)
committerMartin Pitt <martin@piware.de>
Mon, 19 Sep 2022 06:59:29 +0000 (08:59 +0200)
17 files changed:
async-http/404.html [new file with mode: 0644]
async-http/Cargo.toml [new file with mode: 0644]
async-http/index.html [new file with mode: 0644]
async-http/src/main.rs [new file with mode: 0644]
gtk3-hello-world/Cargo.toml [new file with mode: 0644]
gtk3-hello-world/src/main.rs [new file with mode: 0644]
gtk4-hello-world/Cargo.toml [new file with mode: 0644]
gtk4-hello-world/src/main.rs [new file with mode: 0644]
hyper-server/Cargo.toml [new file with mode: 0644]
hyper-server/src/main.rs [new file with mode: 0644]
serde/src/main.rs
tokio-tutorial-jbarszczewski/Cargo.toml [new file with mode: 0644]
tokio-tutorial-jbarszczewski/src/main.rs [new file with mode: 0644]
tokio-tutorial-mini-redis/Cargo.toml [new file with mode: 0644]
tokio-tutorial-mini-redis/examples/hello-redis.rs [new file with mode: 0644]
tokio-tutorial-mini-redis/src/bin/client.rs [new file with mode: 0644]
tokio-tutorial-mini-redis/src/bin/server.rs [new file with mode: 0644]

diff --git a/async-http/404.html b/async-http/404.html
new file mode 100644 (file)
index 0000000..d59a923
--- /dev/null
@@ -0,0 +1,11 @@
+<!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>
diff --git a/async-http/Cargo.toml b/async-http/Cargo.toml
new file mode 100644 (file)
index 0000000..30b4930
--- /dev/null
@@ -0,0 +1,15 @@
+[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"]
diff --git a/async-http/index.html b/async-http/index.html
new file mode 100644 (file)
index 0000000..fe442d6
--- /dev/null
@@ -0,0 +1,11 @@
+<!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>
diff --git a/async-http/src/main.rs b/async-http/src/main.rs
new file mode 100644 (file)
index 0000000..1a8dddb
--- /dev/null
@@ -0,0 +1,115 @@
+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()));
+    }
+}
diff --git a/gtk3-hello-world/Cargo.toml b/gtk3-hello-world/Cargo.toml
new file mode 100644 (file)
index 0000000..0d0f585
--- /dev/null
@@ -0,0 +1,9 @@
+[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"
diff --git a/gtk3-hello-world/src/main.rs b/gtk3-hello-world/src/main.rs
new file mode 100644 (file)
index 0000000..a44bc62
--- /dev/null
@@ -0,0 +1,41 @@
+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();
+}
diff --git a/gtk4-hello-world/Cargo.toml b/gtk4-hello-world/Cargo.toml
new file mode 100644 (file)
index 0000000..8a60626
--- /dev/null
@@ -0,0 +1,9 @@
+[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" }
diff --git a/gtk4-hello-world/src/main.rs b/gtk4-hello-world/src/main.rs
new file mode 100644 (file)
index 0000000..4ebedc0
--- /dev/null
@@ -0,0 +1,36 @@
+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(move |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();
+}
diff --git a/hyper-server/Cargo.toml b/hyper-server/Cargo.toml
new file mode 100644 (file)
index 0000000..0d9bdfe
--- /dev/null
@@ -0,0 +1,11 @@
+[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"] }
diff --git a/hyper-server/src/main.rs b/hyper-server/src/main.rs
new file mode 100644 (file)
index 0000000..8199471
--- /dev/null
@@ -0,0 +1,42 @@
+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);
+    }
+}
index fc88d23c418c5872db2d7df475506038a1644949..cdc7c81259b5aa4b177fbf2cb8197d7b78b30fc6 100644 (file)
@@ -44,9 +44,9 @@ fn load_contacts() -> Result<Contacts, Box<dyn Error>> {
 }
 
 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(())
 }
diff --git a/tokio-tutorial-jbarszczewski/Cargo.toml b/tokio-tutorial-jbarszczewski/Cargo.toml
new file mode 100644 (file)
index 0000000..86cffc6
--- /dev/null
@@ -0,0 +1,9 @@
+[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"]}
diff --git a/tokio-tutorial-jbarszczewski/src/main.rs b/tokio-tutorial-jbarszczewski/src/main.rs
new file mode 100644 (file)
index 0000000..f998acc
--- /dev/null
@@ -0,0 +1,58 @@
+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();
+}
diff --git a/tokio-tutorial-mini-redis/Cargo.toml b/tokio-tutorial-mini-redis/Cargo.toml
new file mode 100644 (file)
index 0000000..b6caa1b
--- /dev/null
@@ -0,0 +1,13 @@
+[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"] }
diff --git a/tokio-tutorial-mini-redis/examples/hello-redis.rs b/tokio-tutorial-mini-redis/examples/hello-redis.rs
new file mode 100644 (file)
index 0000000..a498655
--- /dev/null
@@ -0,0 +1,17 @@
+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(())
+}
diff --git a/tokio-tutorial-mini-redis/src/bin/client.rs b/tokio-tutorial-mini-redis/src/bin/client.rs
new file mode 100644 (file)
index 0000000..184170e
--- /dev/null
@@ -0,0 +1,57 @@
+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();
+}
diff --git a/tokio-tutorial-mini-redis/src/bin/server.rs b/tokio-tutorial-mini-redis/src/bin/server.rs
new file mode 100644 (file)
index 0000000..962c923
--- /dev/null
@@ -0,0 +1,65 @@
+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(())
+}