diff --git a/README.md b/README.md index b11f3544..6a20d95e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,10 @@ kraft pkg ls --apps --update | ![](./.github/icons/go.svg) | [Simple Go 1.21 HTTP Web Server with `net/http`](https://github.com/unikraft/catalog/tree/main/examples/http-go1.21) | | ![](./.github/icons/python3.svg) | [Simple Flask 3.0 HTTP Web Server](https://github.com/unikraft/catalog/tree/main/examples/http-python3.10-flask3.0) | | ![](./.github/icons/python3.svg) | [Simple Python 3.10 HTTP Web Server with `http.server.HTTPServer`](https://github.com/unikraft/catalog/tree/main/examples/http-python3.10) | +| ![](./.github/icons/rust-white.svg) | [Rust Actix Web Server](https://github.com/unikraft/catalog/tree/main/examples/http-rust1.75-actix-web4) | +| ![](./.github/icons/rust-white.svg) | [Rust/Rocket v0.5](https://github.com/unikraft/catalog/tree/main/examples/http-rust1.75-rocket0.5) | +| ![](./.github/icons/rust-white.svg) | [Rust/Tokio Server](https://github.com/unikraft/catalog/tree/main/examples/http-rust1.75-tokio) | +| ![](./.github/icons/rust-white.svg) | [Rust HTTP Web Server](https://github.com/unikraft/catalog/tree/main/examples/http-rust1.75) | ## Library diff --git a/examples/http-rust1.75-actix-web4/Cargo.toml b/examples/http-rust1.75-actix-web4/Cargo.toml new file mode 100644 index 00000000..1acde444 --- /dev/null +++ b/examples/http-rust1.75-actix-web4/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hello" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +actix-web = "4" diff --git a/examples/http-rust1.75-actix-web4/Dockerfile b/examples/http-rust1.75-actix-web4/Dockerfile new file mode 100644 index 00000000..5433cd9a --- /dev/null +++ b/examples/http-rust1.75-actix-web4/Dockerfile @@ -0,0 +1,15 @@ +FROM rust:1.75.0-bookworm AS build + +RUN cargo new --bin app +WORKDIR /app +COPY Cargo.toml ./ +COPY src ./src +RUN cargo build --release + +FROM scratch + +COPY --from=build /app/target/release/hello /server +COPY --from=build /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/ +COPY --from=build /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/ +COPY --from=build /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/ +COPY --from=build /lib64/ld-linux-x86-64.so.2 /lib64/ diff --git a/examples/http-rust1.75-actix-web4/Kraftfile b/examples/http-rust1.75-actix-web4/Kraftfile new file mode 100644 index 00000000..abe5d412 --- /dev/null +++ b/examples/http-rust1.75-actix-web4/Kraftfile @@ -0,0 +1,7 @@ +spec: v0.6 + +runtime: base:latest + +rootfs: ./Dockerfile + +cmd: ["/server"] diff --git a/examples/http-rust1.75-actix-web4/README.md b/examples/http-rust1.75-actix-web4/README.md new file mode 100644 index 00000000..e132a668 --- /dev/null +++ b/examples/http-rust1.75-actix-web4/README.md @@ -0,0 +1,63 @@ +# Rust Actix Web Server + +This directory contains an [Actix](https://actix.rs/) web server running on Unikraft. + +## Set Up + +To run this example, [install Unikraft's companion command-line toolchain `kraft`](https://unikraft.org/docs/cli), clone this repository and `cd` into this directory. + +## Run and Use + +Use `kraft` to run the image and start a Unikraft instance: + +```bash +kraft run --rm -p 8080:8080 --plat qemu --arch x86_64 +``` + +If the `--plat` argument is left out, it defaults to `qemu`. +If the `--arch` argument is left out, it defaults to your system's CPU architecture. + +Once executed, it will open port `8080` and wait for connections. +To test it, you can use `curl`: + +```bash +curl localhost:8080 +``` + +You should see a "Hello, World!" message. + +## Inspect and Close + +To list information about the Unikraft instance, use: + +```bash +kraft ps +``` +```text +NAME KERNEL ARGS CREATED STATUS MEM PLAT +admiring_ndakasi oci://unikraft.org/base:latest /server 1 minute ago running 64MiB 0.0.0.0:8080->8080/tcp qemu/x86_64 +``` + +The instance name is `nostalgic_snowflake`. +To close the Unikraft instance, close the `kraft` process (e.g., via `Ctrl+c`) or run: + +```bash +kraft rm nostalgic_snowflake +``` + +Note that depending on how you modify this example your instance **may** need more memory to run. +To do so, use the `kraft run`'s `-M` flag, for example: + +```bash +kraft run -p 8080:8080 --plat qemu --arch x86_64 -M 512M +``` + +## `kraft` and `sudo` + +Mixing invocations of `kraft` and `sudo` can lead to unexpected behavior. +Read more about how to start `kraft` without `sudo` at [https://unikraft.org/sudoless](https://unikraft.org/sudoless). + +## Learn More + +- [How to run unikernels locally](https://unikraft.org/docs/cli/running) +- [Building `Dockerfile` Images with `BuildKit`](https://unikraft.org/guides/building-dockerfile-images-with-buildkit) diff --git a/examples/http-rust1.75-actix-web4/src/main.rs b/examples/http-rust1.75-actix-web4/src/main.rs new file mode 100644 index 00000000..f38e1b91 --- /dev/null +++ b/examples/http-rust1.75-actix-web4/src/main.rs @@ -0,0 +1,28 @@ +use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; + +#[get("/")] +async fn hello() -> impl Responder { + HttpResponse::Ok().body("Hello, World!\r\n") +} + +#[post("/echo")] +async fn echo(req_body: String) -> impl Responder { + HttpResponse::Ok().body(req_body) +} + +async fn manual_hello() -> impl Responder { + HttpResponse::Ok().body("Hey there!") +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .service(hello) + .service(echo) + .route("/hey", web::get().to(manual_hello)) + }) + .bind(("0.0.0.0", 8080))? + .run() + .await +} diff --git a/examples/http-rust1.75-rocket0.5/Cargo.toml b/examples/http-rust1.75-rocket0.5/Cargo.toml new file mode 100644 index 00000000..31515f7d --- /dev/null +++ b/examples/http-rust1.75-rocket0.5/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hello" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +rocket = "0.5.0" diff --git a/examples/http-rust1.75-rocket0.5/Dockerfile b/examples/http-rust1.75-rocket0.5/Dockerfile new file mode 100644 index 00000000..7df139f9 --- /dev/null +++ b/examples/http-rust1.75-rocket0.5/Dockerfile @@ -0,0 +1,17 @@ +FROM rust:1.75.0-bookworm AS build + +RUN cargo new --bin app +WORKDIR /app +COPY Cargo.toml ./ +COPY Rocket.toml ./ +COPY src ./src +RUN cargo build --release + +FROM scratch + +COPY ./Rocket.toml /Rocket.toml +COPY --from=build /app/target/release/hello /server +COPY --from=build /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/ +COPY --from=build /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/ +COPY --from=build /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/ +COPY --from=build /lib64/ld-linux-x86-64.so.2 /lib64/ diff --git a/examples/http-rust1.75-rocket0.5/Kraftfile b/examples/http-rust1.75-rocket0.5/Kraftfile new file mode 100644 index 00000000..abe5d412 --- /dev/null +++ b/examples/http-rust1.75-rocket0.5/Kraftfile @@ -0,0 +1,7 @@ +spec: v0.6 + +runtime: base:latest + +rootfs: ./Dockerfile + +cmd: ["/server"] diff --git a/examples/http-rust1.75-rocket0.5/README.md b/examples/http-rust1.75-rocket0.5/README.md new file mode 100644 index 00000000..5c0cbafd --- /dev/null +++ b/examples/http-rust1.75-rocket0.5/README.md @@ -0,0 +1,74 @@ +# Rust/Rocket v0.5 + +This example was derived from [Rocket's "hello" example](https://github.com/rwf2/Rocket/tree/v0.5/examples/hello). + +## Set Up + +To run this example, [install Unikraft's companion command-line toolchain `kraft`](https://unikraft.org/docs/cli), clone this repository and `cd` into this directory. + +## Run and Use + +Use `kraft` to run the image and start a Unikraft instance: + +```bash +kraft run --rm -p 8080:8080 --plat qemu --arch x86_64 +``` + +If the `--plat` argument is left out, it defaults to `qemu`. +If the `--arch` argument is left out, it defaults to your system's CPU architecture. + +Once executed, it will open port `8080` and wait for connections. +To test it, you can use `curl`. +Try one of these available endpoints: + +```bash +Then try visiting one of the available paths: +- https://..kraft.cloud/hello/world +- https://..kraft.cloud/hello/мир +- https://..kraft.cloud/wave/Rocketeer/100 +- https://..kraft.cloud/?emoji +- https://..kraft.cloud/?name=Rocketeer +- https://..kraft.cloud/?lang=ру +- https://..kraft.cloud/?lang=ру&emoji +- https://..kraft.cloud/?emoji&lang=en +- https://..kraft.cloud/?name=Rocketeer&lang=en +- https://..kraft.cloud/?emoji&name=Rocketeer +- https://..kraft.cloud/?name=Rocketeer&lang=en&emoji +- https://..kraft.cloud/?lang=ru&emoji&name=Rocketeer +``` + +## Inspect and Close + +To list information about the Unikraft instance, use: + +```bash +kraft ps +``` +```text +NAME KERNEL ARGS CREATED STATUS MEM PLAT +admiring_ndakasi oci://unikraft.org/base:latest /server 1 minute ago running 64MiB 0.0.0.0:8080->8080/tcp qemu/x86_64 +``` + +The instance name is `nostalgic_snowflake`. +To close the Unikraft instance, close the `kraft` process (e.g., via `Ctrl+c`) or run: + +```bash +kraft rm nostalgic_snowflake +``` + +Note that depending on how you modify this example your instance **may** need more memory to run. +To do so, use the `kraft run`'s `-M` flag, for example: + +```bash +kraft run -p 8080:8080 --plat qemu --arch x86_64 -M 512M +``` + +## `kraft` and `sudo` + +Mixing invocations of `kraft` and `sudo` can lead to unexpected behavior. +Read more about how to start `kraft` without `sudo` at [https://unikraft.org/sudoless](https://unikraft.org/sudoless). + +## Learn More + +- [How to run unikernels locally](https://unikraft.org/docs/cli/running) +- [Building `Dockerfile` Images with `BuildKit`](https://unikraft.org/guides/building-dockerfile-images-with-buildkit) diff --git a/examples/http-rust1.75-rocket0.5/Rocket.toml b/examples/http-rust1.75-rocket0.5/Rocket.toml new file mode 100644 index 00000000..30421bba --- /dev/null +++ b/examples/http-rust1.75-rocket0.5/Rocket.toml @@ -0,0 +1,3 @@ +[default] +address = "0.0.0.0" +port = 8080 diff --git a/examples/http-rust1.75-rocket0.5/src/main.rs b/examples/http-rust1.75-rocket0.5/src/main.rs new file mode 100644 index 00000000..6da0f9ba --- /dev/null +++ b/examples/http-rust1.75-rocket0.5/src/main.rs @@ -0,0 +1,81 @@ +#[macro_use] extern crate rocket; + +#[cfg(test)] mod tests; + +#[derive(FromFormField)] +enum Lang { + #[field(value = "en")] + English, + #[field(value = "ru")] + #[field(value = "ру")] + Russian +} + +#[derive(FromForm)] +struct Options<'r> { + emoji: bool, + name: Option<&'r str>, +} + +// Try visiting: +// http://127.0.0.1:8000/hello/world +#[get("/world")] +fn world() -> &'static str { + "Hello, World!" +} + +// Try visiting: +// http://127.0.0.1:8000/hello/мир +#[get("/мир")] +fn mir() -> &'static str { + "Привет, мир!" +} + +// Try visiting: +// http://127.0.0.1:8000/wave/Rocketeer/100 +#[get("//")] +fn wave(name: &str, age: u8) -> String { + format!("👋 Hello, {} year old named {}!", age, name) +} + +// Note: without the `..` in `opt..`, we'd need to pass `opt.emoji`, `opt.name`. +// +// Try visiting: +// http://127.0.0.1:8000/?emoji +// http://127.0.0.1:8000/?name=Rocketeer +// http://127.0.0.1:8000/?lang=ру +// http://127.0.0.1:8000/?lang=ру&emoji +// http://127.0.0.1:8000/?emoji&lang=en +// http://127.0.0.1:8000/?name=Rocketeer&lang=en +// http://127.0.0.1:8000/?emoji&name=Rocketeer +// http://127.0.0.1:8000/?name=Rocketeer&lang=en&emoji +// http://127.0.0.1:8000/?lang=ru&emoji&name=Rocketeer +#[get("/?&")] +fn hello(lang: Option, opt: Options<'_>) -> String { + let mut greeting = String::new(); + if opt.emoji { + greeting.push_str("👋 "); + } + + match lang { + Some(Lang::Russian) => greeting.push_str("Привет"), + Some(Lang::English) => greeting.push_str("Hello"), + None => greeting.push_str("Hi"), + } + + if let Some(name) = opt.name { + greeting.push_str(", "); + greeting.push_str(name); + } + + greeting.push('!'); + greeting +} + +#[launch] +fn rocket() -> _ { + rocket::build() + .mount("/", routes![hello]) + .mount("/hello", routes![world, mir]) + .mount("/wave", routes![wave]) +} diff --git a/examples/http-rust1.75-tokio/Cargo.toml b/examples/http-rust1.75-tokio/Cargo.toml new file mode 100644 index 00000000..b11757de --- /dev/null +++ b/examples/http-rust1.75-tokio/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "http-tokio" +version = "0.1.0" +edition = "2021" + + +[dependencies] +tokio = {version = "1", features = ["rt-multi-thread", "net", "time", "macros", "io-util"] } diff --git a/examples/http-rust1.75-tokio/Dockerfile b/examples/http-rust1.75-tokio/Dockerfile new file mode 100644 index 00000000..dd58201b --- /dev/null +++ b/examples/http-rust1.75-tokio/Dockerfile @@ -0,0 +1,16 @@ +FROM rust:1.75.0-bookworm AS build + +WORKDIR /src + +COPY ./src /src/src +COPY ./Cargo.toml /src/Cargo.toml + +RUN cargo build + +FROM scratch + +COPY --from=build src/target/debug/http-tokio /server +COPY --from=build /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 +COPY --from=build /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/libm.so.6 +COPY --from=build /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/libgcc_s.so.1 +COPY --from=build /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 diff --git a/examples/http-rust1.75-tokio/Kraftfile b/examples/http-rust1.75-tokio/Kraftfile new file mode 100644 index 00000000..abe5d412 --- /dev/null +++ b/examples/http-rust1.75-tokio/Kraftfile @@ -0,0 +1,7 @@ +spec: v0.6 + +runtime: base:latest + +rootfs: ./Dockerfile + +cmd: ["/server"] diff --git a/examples/http-rust1.75-tokio/README.md b/examples/http-rust1.75-tokio/README.md new file mode 100644 index 00000000..db423faf --- /dev/null +++ b/examples/http-rust1.75-tokio/README.md @@ -0,0 +1,64 @@ +# Rust/Tokio Server + +This directory contains an [Tokio](https://tokio.rs/) server running on Unikraft. + +## Set Up + +To run this example, [install Unikraft's companion command-line toolchain `kraft`](https://unikraft.org/docs/cli), clone this repository and `cd` into this directory. + +## Run and Use + +Use `kraft` to run the image and start a Unikraft instance: + +```bash +kraft run --rm -p 8080:8080 --plat qemu --arch x86_64 -M 512M +``` + +If the `--plat` argument is left out, it defaults to `qemu`. +If the `--arch` argument is left out, it defaults to your system's CPU architecture. + +Once executed, it will open port `8080` and wait for connections. +To test it, you can use `curl`: + +```bash +curl localhost:8080 +``` + +You should see a "Hello, World!" message. + +## Inspect and Close + +To list information about the Unikraft instance, use: + +```bash +kraft ps +``` + +```text +NAME KERNEL ARGS CREATED STATUS MEM PLAT +admiring_ndakasi oci://unikraft.org/base:latest /server 1 minute ago running 512MiB 0.0.0.0:8080->8080/tcp qemu/x86_64 +``` + +The instance name is `nostalgic_snowflake`. +To close the Unikraft instance, close the `kraft` process (e.g., via `Ctrl+c`) or run: + +```bash +kraft rm nostalgic_snowflake +``` + +Note that depending on how you modify this example your instance **may** need more memory to run. +To do so, use the `kraft run`'s `-M` flag, for example: + +```bash +kraft run -p 8080:8080 --plat qemu --arch x86_64 -M 512M +``` + +## `kraft` and `sudo` + +Mixing invocations of `kraft` and `sudo` can lead to unexpected behavior. +Read more about how to start `kraft` without `sudo` at [https://unikraft.org/sudoless](https://unikraft.org/sudoless). + +## Learn More + +- [How to run unikernels locally](https://unikraft.org/docs/cli/running) +- [Building `Dockerfile` Images with `BuildKit`](https://unikraft.org/guides/building-dockerfile-images-with-buildkit) diff --git a/examples/http-rust1.75-tokio/src/main.rs b/examples/http-rust1.75-tokio/src/main.rs new file mode 100644 index 00000000..991b771c --- /dev/null +++ b/examples/http-rust1.75-tokio/src/main.rs @@ -0,0 +1,27 @@ +use std::net::SocketAddr; +use tokio::net::TcpListener; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = SocketAddr::from(([0, 0, 0, 0], 8080)); + let listener = TcpListener::bind(&addr).await?; + + println!("Listening on: http://{}", addr); + + loop { + let (mut stream, _) = listener.accept().await?; + + tokio::spawn(async move { + loop { + let mut buffer = [0; 1024]; + let _ = stream.read(&mut buffer).await; + + let contents = "Hello, World!\r\n"; + let content_length = contents.len(); + let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {content_length}\r\n\r\n{contents}"); + let _ = stream.write_all(response.as_bytes()).await; + } + }); + } +} diff --git a/examples/http-rust1.75/Dockerfile b/examples/http-rust1.75/Dockerfile new file mode 100644 index 00000000..82c03acf --- /dev/null +++ b/examples/http-rust1.75/Dockerfile @@ -0,0 +1,15 @@ +FROM rust:1.75.0-bookworm AS build + +WORKDIR /src + +COPY ./server.rs /src/server.rs + +RUN set -xe; \ + rustc -o /server /src/server.rs + +FROM scratch + +COPY --from=build /server /server +COPY --from=build /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/ +COPY --from=build /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/ +COPY --from=build /lib64/ld-linux-x86-64.so.2 /lib64/ diff --git a/examples/http-rust1.75/Kraftfile b/examples/http-rust1.75/Kraftfile new file mode 100644 index 00000000..abe5d412 --- /dev/null +++ b/examples/http-rust1.75/Kraftfile @@ -0,0 +1,7 @@ +spec: v0.6 + +runtime: base:latest + +rootfs: ./Dockerfile + +cmd: ["/server"] diff --git a/examples/http-rust1.75/README.md b/examples/http-rust1.75/README.md new file mode 100644 index 00000000..b5e24f46 --- /dev/null +++ b/examples/http-rust1.75/README.md @@ -0,0 +1,64 @@ +# Rust HTTP Web Server + +This directory contains a [Rust](https://www.rust-lang.org/) HTTP server running on Unikraft. + +## Set Up + +To run this example, [install Unikraft's companion command-line toolchain `kraft`](https://unikraft.org/docs/cli), clone this repository and `cd` into this directory. + +## Run and Use + +Use `kraft` to run the image and start a Unikraft instance: + +```bash +kraft run --rm -p 8080:8080 --plat qemu --arch x86_64 +``` + +If the `--plat` argument is left out, it defaults to `qemu`. +If the `--arch` argument is left out, it defaults to your system's CPU architecture. + +Once executed, it will open port `8080` and wait for connections. +To test it, you can use `curl`: + +```bash +curl localhost:8080 +``` + +You should see a "Hello, World!" message. + +## Inspect and Close + +To list information about the Unikraft instance, use: + +```bash +kraft ps +``` + +```text +NAME KERNEL ARGS CREATED STATUS MEM PLAT +admiring_ndakasi oci://unikraft.org/base:latest /server 1 minute ago running 64MiB 0.0.0.0:8080->8080/tcp qemu/x86_64 +``` + +The instance name is `nostalgic_snowflake`. +To close the Unikraft instance, close the `kraft` process (e.g., via `Ctrl+c`) or run: + +```bash +kraft rm nostalgic_snowflake +``` + +Note that depending on how you modify this example your instance **may** need more memory to run. +To do so, use the `kraft run`'s `-M` flag, for example: + +```bash +kraft run -p 8080:8080 --plat qemu --arch x86_64 -M 512M +``` + +## `kraft` and `sudo` + +Mixing invocations of `kraft` and `sudo` can lead to unexpected behavior. +Read more about how to start `kraft` without `sudo` at [https://unikraft.org/sudoless](https://unikraft.org/sudoless). + +## Learn More + +- [How to run unikernels locally](https://unikraft.org/docs/cli/running) +- [Building `Dockerfile` Images with `BuildKit`](https://unikraft.org/guides/building-dockerfile-images-with-buildkit) diff --git a/examples/http-rust1.75/server.rs b/examples/http-rust1.75/server.rs new file mode 100644 index 00000000..25317574 --- /dev/null +++ b/examples/http-rust1.75/server.rs @@ -0,0 +1,50 @@ +// https://gist.github.com/mjohnsullivan/e5182707caf0a9dbdf2d +// Updated example from http://rosettacode.org/wiki/Hello_world/Web_server#Rust +// to work with Rust 1.0 beta + +use std::net::{TcpStream, TcpListener}; +use std::io::{Read, Write}; +use std::thread; + + +fn handle_read(mut stream: &TcpStream) { + let mut buf = [0u8 ;4096]; + match stream.read(&mut buf) { + Ok(_) => { + let req_str = String::from_utf8_lossy(&buf); + println!("{}", req_str); + }, + Err(e) => println!("Unable to read stream: {}", e), + } +} + +fn handle_write(mut stream: TcpStream) { + let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\nHello, World!\r\n"; + match stream.write(response) { + Ok(_) => println!("Response sent"), + Err(e) => println!("Failed sending response: {}", e), + } +} + +fn handle_client(stream: TcpStream) { + handle_read(&stream); + handle_write(stream); +} + +fn main() { + let listener = TcpListener::bind("0.0.0.0:8080").unwrap(); + println!("Listening for connections on port {}", 8080); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + thread::spawn(|| { + handle_client(stream) + }); + } + Err(e) => { + println!("Unable to connect: {}", e); + } + } + } +}