diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/my_sql_driver.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/my_sql_driver.md index ea9c7abc..79c18a54 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/my_sql_driver.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/my_sql_driver.md @@ -8,7 +8,8 @@ The database connection is necessary for today's enterprise development. WasmEdg :::note -Before we start, ensure [you have Rust and WasmEdge installed](../setup.md). If you are connecting to a remote MySQL database using TLS, you will need to [install the TLS plugin](../../../start/install.md#tls-plug-in) for WasmEdge as well. +Before we start, [you need to have Rust and WasmEdge installed](../setup.md). +Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac. ::: ## Run the example @@ -20,18 +21,18 @@ git clone https://github.com/WasmEdge/wasmedge-db-examples cd wasmedge-db-examples/mysql_async # Compile the rust code into WASM -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Execute MySQL statements against a MySQL database at mysql://user:passwd@127.0.0.1:3306 wasmedge --env "DATABASE_URL=mysql://user:passwd@127.0.0.1:3306/mysql" target/wasm32-wasi/release/crud.wasm ``` -To use TLS, you will need to turn on the `default-rustls` feature in `Cargo.toml`. - -```toml -mysql_async_wasi = { version = "0.31", features = [ "default-rustls" ] } -``` + +:::note +Since we have TLS enabled by default in this example, you will need the [wasi-sdk version of clang](../setup#tls-on-macos) for compiling it on MacOS. +::: +To use TLS, you will need to turn on the `default-rustls` feature on the `mysql_async` crate in `Cargo.toml`. Then, run the application as follows. ```toml @@ -39,9 +40,182 @@ Then, run the application as follows. wasmedge --env "DATABASE_SSL=1" --env "DATABASE_URL=mysql://user:passwd@mydb.123456789012.us-east-1.rds.amazonaws.com:3306/mysql" crud.wasm ``` -## Code explanation +## Configuration + +In order to compile the `mysql_async` and `tokio` crates, we will need to apply two patches to add +WasmEdge-specific socket APIs to those crates. The following example shows that the TLS connection is enabled. + +``` +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } + +[dependencies] +mysql_async = { version = "0.34", default-features=false, features = [ "default-rustls" ], git="https://github.com/blackbeam/mysql_async.git" } +zstd-sys = "=2.0.9" +tokio = { version = "1", features = [ "io-util", "fs", "net", "time", "rt", "macros"] } +``` + +## Code example + +The following code shows how to connect to a MySQL database server, and then insert, update, and delete records using SQL +statements. + +Connect to a MySQL database. + +``` + // Below we create a customized connection pool + let opts = Opts::from_url(&*get_url()).unwrap(); + let mut builder = OptsBuilder::from_opts(opts); + if std::env::var("DATABASE_SSL").is_ok() { + builder = builder.ssl_opts(SslOpts::default()); + } + // The connection pool will have a min of 5 and max of 10 connections. + let constraints = PoolConstraints::new(5, 10).unwrap(); + let pool_opts = PoolOpts::default().with_constraints(constraints); + + let pool = Pool::new(builder.pool_opts(pool_opts)); + let mut conn = pool.get_conn().await.unwrap(); +``` + +Create a table on the connected database. + +``` + // create table if no tables exist + let result = r"SHOW TABLES LIKE 'orders';" + .with(()) + .map(&mut conn, |s: String| String::from(s)) + .await?; + + if result.len() == 0 { + // table doesn't exist, create a new one + r"CREATE TABLE orders (order_id INT, production_id INT, quantity INT, amount FLOAT, shipping FLOAT, tax FLOAT, shipping_address VARCHAR(20));".ignore(&mut conn).await?; + println!("create new table"); + } else { + // delete all data from the table. + println!("delete all from orders"); + r"DELETE FROM orders;".ignore(&mut conn).await?; + } +``` + +Insert some records into the MySQL database using SQL. + +``` + let orders = vec![ + Order::new(1, 12, 2, 56.0, 15.0, 2.0, String::from("Mataderos 2312")), + Order::new(2, 15, 3, 256.0, 30.0, 16.0, String::from("1234 NW Bobcat")), + Order::new(3, 11, 5, 536.0, 50.0, 24.0, String::from("20 Havelock")), + Order::new(4, 8, 8, 126.0, 20.0, 12.0, String::from("224 Pandan Loop")), + Order::new(5, 24, 1, 46.0, 10.0, 2.0, String::from("No.10 Jalan Besar")), + ]; + + r"INSERT INTO orders (order_id, production_id, quantity, amount, shipping, tax, shipping_address) + VALUES (:order_id, :production_id, :quantity, :amount, :shipping, :tax, :shipping_address)" + .with(orders.iter().map(|order| { + params! { + "order_id" => order.order_id, + "production_id" => order.production_id, + "quantity" => order.quantity, + "amount" => order.amount, + "shipping" => order.shipping, + "tax" => order.tax, + "shipping_address" => &order.shipping_address, + } + })) + .batch(&mut conn) + .await?; +``` + +Query the database. + +``` + // query data + let loaded_orders = "SELECT * FROM orders" + .with(()) + .map( + &mut conn, + |(order_id, production_id, quantity, amount, shipping, tax, shipping_address)| { + Order::new( + order_id, + production_id, + quantity, + amount, + shipping, + tax, + shipping_address, + ) + }, + ) + .await?; + dbg!(loaded_orders.len()); + dbg!(loaded_orders); +``` + +Delete some records from the database. + +``` + // // delete some data + r"DELETE FROM orders WHERE order_id=4;" + .ignore(&mut conn) + .await?; + + // query data + let loaded_orders = "SELECT * FROM orders" + .with(()) + .map( + &mut conn, + |(order_id, production_id, quantity, amount, shipping, tax, shipping_address)| { + Order::new( + order_id, + production_id, + quantity, + amount, + shipping, + tax, + shipping_address, + ) + }, + ) + .await?; + dbg!(loaded_orders.len()); + dbg!(loaded_orders); +``` + +Update records in the MySQL database. + +``` + // // update some data + r"UPDATE orders + SET shipping_address = '8366 Elizabeth St.' + WHERE order_id = 2;" + .ignore(&mut conn) + .await?; + // query data + let loaded_orders = "SELECT * FROM orders" + .with(()) + .map( + &mut conn, + |(order_id, production_id, quantity, amount, shipping, tax, shipping_address)| { + Order::new( + order_id, + production_id, + quantity, + amount, + shipping, + tax, + shipping_address, + ) + }, + ) + .await?; + dbg!(loaded_orders.len()); + dbg!(loaded_orders); +``` + +Close the database connection. + +``` + drop(conn); + pool.disconnect().await.unwrap(); +``` - -:::info -Work in Progress -::: diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/postgres_driver.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/postgres_driver.md index 7095ccfe..5fa8d0fa 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/postgres_driver.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/postgres_driver.md @@ -8,7 +8,8 @@ A database connection is necessary for today's enterprise development. WasmEdge :::note -Before we start, make sure [you have Rust and WasmEdge installed](../setup.md). +Before we start, [you need to have Rust and WasmEdge installed](../setup.md). +Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac. ::: ## Run the example @@ -20,15 +21,134 @@ git clone https://github.com/WasmEdge/wasmedge-db-examples cd wasmedge-db-examples/postgres # Compile the rust code into WASM -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Execute SQL statements against a PostgreSQL database at postgres://user:passwd@localhost/testdb wasmedge --env "DATABASE_URL=postgres://user:passwd@localhost/testdb" target/wasm32-wasi/release/crud.wasm ``` +## Configuration + +In order to compile the `tokio-postgres` and `tokio` crates, we will need to apply patches to add WasmEdge-specific socket APIs to those crates in `Cargo.toml`. + +``` +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +tokio-postgres = { git = "https://github.com/second-state/rust-postgres.git" } + +[dependencies] +tokio-postgres = "0.7" +tokio = { version = "1", features = [ + "io-util", + "fs", + "net", + "time", + "rt", + "macros", +] } +``` + ## Code explanation - -:::info -Work in Progress -::: +We first use a Rust struct to represent the database table. + +```rust +#[derive(Debug)] +struct Order { + order_id: i32, + production_id: i32, + quantity: i32, + amount: f32, + shipping: f32, + tax: f32, + shipping_address: String, +} + +impl Order { + fn new( + order_id: i32, + production_id: i32, + quantity: i32, + amount: f32, + shipping: f32, + tax: f32, + shipping_address: String, + ) -> Self { + Self { + order_id, + production_id, + quantity, + amount, + shipping, + tax, + shipping_address, + } + } +} +``` + +Then, you can use the `tokio-postgres` API to access the database through its connection URL. +The code below shows how to perform basic CRUD operations using SQL commands. + +```rust +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Error> { + // Connect to the database. + let (client, connection) = tokio_postgres::connect(&*get_url(), NoTls).await?; + + // The connection object performs the actual communication with the database, + // so spawn it off to run on its own. + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + + client.execute("CREATE TABLE IF NOT EXISTS orders (order_id INT, production_id INT, quantity INT, amount REAL, shipping REAL, tax REAL, shipping_address VARCHAR(256));", &[]).await?; + + let orders = vec![ + Order::new(1, 12, 2, 56.0, 15.0, 2.0, String::from("Mataderos 2312")), + Order::new(2, 15, 3, 256.0, 30.0, 16.0, String::from("1234 NW Bobcat")), + Order::new(3, 11, 5, 536.0, 50.0, 24.0, String::from("20 Havelock")), + Order::new(4, 8, 8, 126.0, 20.0, 12.0, String::from("224 Pandan Loop")), + Order::new(5, 24, 1, 46.0, 10.0, 2.0, String::from("No.10 Jalan Besar")), + ]; + + for order in orders.iter() { + client.execute( + "INSERT INTO orders (order_id, production_id, quantity, amount, shipping, tax, shipping_address) VALUES ($1, $2, $3, $4, $5, $6, $7)", + &[&order.order_id, &order.production_id, &order.quantity, &order.amount, &order.shipping, &order.tax, &order.shipping_address] + ).await?; + } + + let rows = client.query("SELECT * FROM orders;", &[]).await?; + for row in rows.iter() { + let order_id : i32 = row.get(0); + println!("order_id {}", order_id); + + let production_id : i32 = row.get(1); + println!("production_id {}", production_id); + + let quantity : i32 = row.get(2); + println!("quantity {}", quantity); + + let amount : f32 = row.get(3); + println!("amount {}", amount); + + let shipping : f32 = row.get(4); + println!("shipping {}", shipping); + + let tax : f32 = row.get(5); + println!("tax {}", tax); + + let shipping_address : &str = row.get(6); + println!("shipping_address {}", shipping_address); + } + + client.execute("DELETE FROM orders;", &[]).await?; + + Ok(()) +} +``` + diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/qdrant_driver.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/qdrant_driver.md new file mode 100644 index 00000000..bcb3bb27 --- /dev/null +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/qdrant_driver.md @@ -0,0 +1,132 @@ +--- +sidebar_position: 4 +--- + +# Qdrant driver + +WasmEdge is emerging as a lightweight, portable, and embeddable runtime for large language models (LLMs). LLM inference applications, such as RAG chatbots and AI agents, can be developed on Mac or Windows, compiled to Wasm once, and then deployed across Nvidia / AMD / ARM-powered devices or servers, fully taking advantage of on-device GPUs, NPUs, and accelerators. + +Hence, besides the LLM inference runtime, those LLM applications also need to manage embeddings in vector databases. The [qdrant-rest-client](https://crates.io/crates/qdrant_rest_client) crate allows you to access the Qdrant vector database from your portable Wasm apps! + + +:::note +Before we start, [you need to have Rust and WasmEdge installed](../setup.md). +Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac. +::: + +## Run the example + +The [wasmedge-db-example/qdrant](https://github.com/WasmEdge/wasmedge-db-examples/tree/main/qdrant) is a Qdrant client example written in Rust. + +```bash +git clone https://github.com/WasmEdge/wasmedge-db-examples +cd wasmedge-db-examples/qdrant + +# Compile the rust code into WASM +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release + +# Perform vector data operations against a Qdrant at http://localhost:6333 +wasmedge target/wasm32-wasi/release/qdrant_examples.wasm +``` + +## Configuration + +In order to compile the `qdrant_rest_client` and `tokio` crates, we will need to apply patches to add WasmEdge-specific socket APIs to those crates in `Cargo.toml`. + +```rust +[patch.crates-io] +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +reqwest = { git = "https://github.com/second-state/wasi_reqwest.git", branch = "0.11.x" } +hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" } +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } + +[dependencies] +anyhow = "1.0" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +url = "2.3" +tokio = { version = "1", features = ["io-util", "fs", "net", "time", "rt", "macros"] } +qdrant_rest_client = "0.1.0" +``` + +## Code explanation + +The following program uses the `qdrant_rest_client` crate to access local Qdrant server through its RESTful API. +It first creates several points (vectors), saves those vectors to the Qdrant database, retrieves some vectors, +searches for vectors, and finally deletes them from the database. + +```rust +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<(), Box> { + let client = qdrant::Qdrant::new(); + // Create a collection with 10-dimensional vectors + let r = client.create_collection("my_test", 4).await; + println!("Create collection result is {:?}", r); + + let mut points = Vec::::new(); + points.push(Point { + id: PointId::Num(1), + vector: vec![0.05, 0.61, 0.76, 0.74], + payload: json!({"city": "Berlin"}).as_object().map(|m| m.to_owned()), + }); + points.push(Point { + id: PointId::Num(2), + vector: vec![0.19, 0.81, 0.75, 0.11], + payload: json!({"city": "London"}).as_object().map(|m| m.to_owned()), + }); + points.push(Point { + id: PointId::Num(3), + vector: vec![0.36, 0.55, 0.47, 0.94], + payload: json!({"city": "Moscow"}).as_object().map(|m| m.to_owned()), + }); + points.push(Point { + id: PointId::Num(4), + vector: vec![0.18, 0.01, 0.85, 0.80], + payload: json!({"city": "New York"}) + .as_object() + .map(|m| m.to_owned()), + }); + points.push(Point { + id: PointId::Num(5), + vector: vec![0.24, 0.18, 0.22, 0.44], + payload: json!({"city": "Beijing"}).as_object().map(|m| m.to_owned()), + }); + points.push(Point { + id: PointId::Num(6), + vector: vec![0.35, 0.08, 0.11, 0.44], + payload: json!({"city": "Mumbai"}).as_object().map(|m| m.to_owned()), + }); + + let r = client.upsert_points("my_test", points).await; + println!("Upsert points result is {:?}", r); + + println!( + "The collection size is {}", + client.collection_info("my_test").await + ); + + let p = client.get_point("my_test", 2).await; + println!("The second point is {:?}", p); + + let ps = client.get_points("my_test", vec![1, 2, 3, 4, 5, 6]).await; + println!("The 1-6 points are {:?}", ps); + + let q = vec![0.2, 0.1, 0.9, 0.7]; + let r = client.search_points("my_test", q, 2, None).await; + println!("Search result points are {:?}", r); + + let r = client.delete_points("my_test", vec![1, 4]).await; + println!("Delete points result is {:?}", r); + + println!( + "The collection size is {}", + client.collection_info("my_test").await + ); + + let q = vec![0.2, 0.1, 0.9, 0.7]; + let r = client.search_points("my_test", q, 2, None).await; + println!("Search result points are {:?}", r); + Ok(()) +} +``` + diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/redis_driver.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/redis_driver.md index c2c1afd7..ce07650d 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/redis_driver.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/database/redis_driver.md @@ -8,7 +8,8 @@ WasmEdge provides a Redis driver for Rust developers, enabling developers to bui :::note -Before we start, ensure [you have Rust and WasmEdge installed](../setup.md). +Before we start, [you need to have Rust and WasmEdge installed](../setup.md). +Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac. ::: ## Run the example @@ -20,15 +21,53 @@ git clone https://github.com/WasmEdge/wasmedge-db-examples cd wasmedge-db-examples/redis # Compile the rust code into WASM -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Execute Redis command against a Redis instance at redis://localhost/ wasmedge --env "REDIS_URL=redis://localhost/" target/wasm32-wasi/release/wasmedge-redis-client-examples.wasm ``` +## Configuration + +In order to compile the `redis` and `tokio` crates, we will need to apply patches to add WasmEdge-specific socket APIs to those crates in `Cargo.toml`. + +```rust +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } + +[dependencies] +anyhow = "1.0" +chrono = { version = "0.4", features = ["serde"] } +tokio = { version = "1", features = ["full"] } +redis = { version = "0.25.4", default-features = false, features = [ + "tokio-comp", +] } +``` + ## Code explanation - -:::info -Work in Progress -::: +The following program uses the `redis` crate to access a Redis server through its connection URL. +It gets the current time, saves the timestamp object to the Redis server, and then reads it back for +display on the console. + +```rust +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + // connect to redis + let client = redis::Client::open(&*get_url()).unwrap(); + let mut con = client.get_multiplexed_async_connection().await.unwrap(); + + let time = format!("{}", chrono::Utc::now()); + // throw away the result, just make sure it does not fail + let _: () = con.set("current_time", time).await.unwrap(); + + // read back the key and return it. Because the return value + // from the function is a result for String, this will automatically + // convert into one. + let value: String = con.get("current_time").await.unwrap(); + println!("Successfully GET `time`: {}", value); + + Ok(()) +} +``` + diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/client.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/client.md index 868bcd01..c96bc306 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/client.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/client.md @@ -8,18 +8,18 @@ WasmEdge allows Rust developers to use APIs they are already familiar with to ac :::note -Before we start, ensure [you have Rust and WasmEdge installed](../setup.md). To make HTTPS requests, install the [WasmEdge TLS plug-in](../../../start/install.md#tls-plug-in). +Before we start, [you need to have Rust and WasmEdge installed](../setup.md). +Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac. ::: We will discuss HTTP and HTTPS clients using popular Rust APIs. - [The reqwest API](#the-reqwest-api) - [The hyper API](#the-hyper-api) -- [The http_req API](#the-http_req-api) ## The reqwest API -The `reqwest` crate is a popular Rust library to create asynchronous HTTP clients. It is built on top of the `hyper` and `tokio` APIs. Many developers find it easier to use. But perhaps more importantly, many existing Rust applications use `reqwest`, and you can make them work in WasmEdge by simply replacing the `reqwest` crate with `reqwest_wasi`! Build and run [the example](https://github.com/WasmEdge/wasmedge_reqwest_demo/) in WasmEdge as follows. +The `reqwest` crate is a popular Rust library to create asynchronous HTTP clients. It is built on top of the `hyper` and `tokio` APIs. Many developers find it easier to use. But perhaps more importantly, many existing Rust applications use `reqwest`, and you can make them work in WasmEdge by simply patching the `reqwest` crate in `Cargo.toml` with simple patches! Build and run [the example](https://github.com/WasmEdge/wasmedge_reqwest_demo/) in WasmEdge as follows. :::note @@ -31,7 +31,7 @@ git clone https://github.com/WasmEdge/wasmedge_reqwest_demo cd wasmedge_reqwest_demo # Build the Rust code -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Use the AoT compiler to get better performance wasmedge compile target/wasm32-wasi/release/http.wasm http.wasm wasmedge compile target/wasm32-wasi/release/https.wasm https.wasm @@ -43,14 +43,25 @@ wasmedge http.wasm wasmedge https.wasm ``` -In your Rust application, import [the WasmEdge adapted reqwest crate](https://crates.io/crates/reqwest_wasi), which uses a special version of single-threaded Tokio that is adapted for WebAssembly. Just add the following lines to your `Cargo.toml`. +In your Rust application, import the standard [reqwest](https://crates.io/crates/reqwest) and [tokio](https://crates.io/crates/tokio) crates. You will also patch a few dependency crates to make them aware of the WasmEdge socket API. Just add the following lines to your `Cargo.toml`. ```toml +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" } +reqwest = { git = "https://github.com/second-state/wasi_reqwest.git", branch = "0.11.x" } + [dependencies] -reqwest_wasi = { version = "0.11", features = ["json"] } -tokio_wasi = { version = "1.21", features = ["full"] } +reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"] } +tokio = { version = "1", features = ["rt", "macros", "net", "time"] } ``` + +:::note +The `Cargo.toml` here shows that TLS is enabled. If you need to compile it on the MacOS, you will need the [wasi-sdk version of clang](../setup#tls-on-macos). +::: + The [example Rust code](https://github.com/WasmEdge/wasmedge_reqwest_demo/blob/main/src/http.rs) below shows an HTTP GET request. ```rust @@ -82,23 +93,6 @@ And here is an HTTP POST request. println!("POST: {}", body); ``` -### HTTPS support - -In order to make HTTPS requests from `reqwest`, you will need to install WasmEdge with [the TLS plug-in](../../../start/install.md#tls-plug-in). - -```bash -curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- --plugins wasmedge_rustls -``` - -Then, in `Cargo.toml`, you should enable the TLS feature. - -```toml -reqwest_wasi = { version = "0.11", features = ["wasmedge-tls"] } -tokio_wasi = { version = "1", features = ["rt", "macros", "net", "time"] } -``` - -Then, you can just replace the URLs in the above examples from `http` and `https`. [See details here](https://github.com/WasmEdge/wasmedge_reqwest_demo/blob/main/src/https.rs). - ## The hyper API The [hyper crate](https://crates.io/crates/hyper) is a widely used Rust library to create HTTP and HTTPS networking applications for both clients and servers. A key feature of the `hyper` crate is that it is based on the `tokio` runtime, which supports asynchronous network connections. Asynchronous HTTP or HTTPS requests do not block the execution of the calling application. It allows an application to make multiple concurrent HTTP requests and to process responses as they are received. That enables high-performance networking applications in WasmEdge. Build and run [the hyper example](https://github.com/WasmEdge/wasmedge_hyper_demo/) in WasmEdge as follows. @@ -108,7 +102,7 @@ git clone https://github.com/WasmEdge/wasmedge_hyper_demo cd wasmedge_hyper_demo/client # Build the Rust code -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Use the AoT compiler to get better performance wasmedge compile target/wasm32-wasi/release/wasmedge_hyper_client.wasm wasmedge_hyper_client.wasm @@ -116,25 +110,56 @@ wasmedge compile target/wasm32-wasi/release/wasmedge_hyper_client.wasm wasmedge_ wasmedge wasmedge_hyper_client.wasm ``` -The HTTPS version of the demo is as follows. Make sure that you install the [WasmEdge TLS plug-in](../../../start/install.md#tls-plug-in) first. +In your Rust application, import the [hyper](https://crates.io/crates/hyper) crate, +and patch it with WasmEdge sockets patches. +Just add the following line to your `Cargo.toml`. + +```toml +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" } + +[dependencies] +hyper = { version = "0.14", features = ["full"] } +tokio = { version = "1", features = [ "rt", "macros", "net", "time", "io-util" ] } +``` + +The HTTPS version of the demo is as follows. ```bash // Build cd wasmedge_hyper_demo/client-https -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release wasmedge compile target/wasm32-wasi/release/wasmedge_hyper_client_https.wasm wasmedge_hyper_client_https.wasm // Run wasmedge wasmedge_hyper_client_https.wasm ``` -In your Rust application, import [the WasmEdge adapted hyper crate](https://crates.io/crates/hyper_wasi), which uses a special version of single-threaded Tokio that is adapted for WebAssembly. Just add the following line to your `Cargo.toml`. +In the HTTPS version of `Cargo.toml`, you just need to import the standard [hyper-rustls](https://crates.io/crates/hyper-rustls), [rustls](https://crates.io/crates/rustls) and [webpki-roots](https://crates.io/crates/webpki-roots) crates with the same patches as above. + +``` +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" } -```toml [dependencies] -hyper_wasi = "0.15.0" +hyper = { version = "0.14", features = ["full"]} +hyper-rustls = { version = "0.25", default-features = false, features = [ "http1", "tls12", "logging", "ring", "webpki-tokio" ] } +rustls = { version = "0.22", default-features = false } +webpki-roots = "0.26.1" + +tokio = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]} +pretty_env_logger = "0.4.0" ``` + +:::note +If you need to compile `rustls` as shown in the `Cargo.toml` above on the MacOS, you will need the [wasi-sdk version of clang](../setup#compile-rust-tls-on-macos). +::: + The [Rust example code](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/client/src/main.rs) below shows an HTTP GET request. ```rust @@ -188,60 +213,3 @@ async fn post_url_return_str (url: hyper::Uri, post_body: &'static [u8]) -> Resu } ``` -## The http_req API - -If your WasmEdge application only needs to make sequential requests to external web services, a synchronous client is easier to work with. It allows you to make a request, wait for the response, and move on once the response is fully received. Use the `http_req` API to make simple synchronous HTTP requests. Build and run [the example](https://github.com/second-state/http_req/) in WasmEdge. - -```bash -git clone https://github.com/second-state/http_req -cd http_req/ - -# Build the Rust Code -cargo build --target wasm32-wasi --release -# Use the AoT compiler to get better performance -wasmedge compile target/wasm32-wasi/release/get.wasm get.wasm - -# Run the example -wasmedge get.wasm -... ... -wasmedge get_https.wasm -... ... -``` - -In your Rust application, import the [http_req_wasi](https://crates.io/crates/http_req_wasi) crate, which is compatible with [http_req](https://github.com/jayjamesjay/http_req) at the API level. Just add the following line to your `Cargo.toml`. - -```toml -[dependencies] -http_req_wasi = "0.10" -``` - -The example below shows an [HTTP GET request](https://github.com/second-state/http_req/blob/master/examples/get.rs). For HTTPS requests, you can [simply change](https://github.com/second-state/http_req/blob/master/examples/get_https.rs) the `http` URL to `https`. - -```rust -use http_req::request; - -fn main() { - let mut writer = Vec::new(); //container for body of a response - let res = request::get("http://eu.httpbin.org/get?msg=WasmEdge", &mut writer).unwrap(); - - println!("Status: {} {}", res.status_code(), res.reason()); - println!("Headers {}", res.headers()); - println!("{}", String::from_utf8_lossy(&writer)); -} -``` - -And here is an [HTTP POST request](https://github.com/second-state/http_req/blob/master/examples/post.rs). For HTTPS requests, you can [simply change](https://github.com/second-state/http_req/blob/master/examples/post_https.rs) the `http` URL to `https`. - -```rust -use http_req::request; - -fn main() { - let mut writer = Vec::new(); //container for body of a response - const BODY: &[u8; 27] = b"field1=value1&field2=value2"; - let res = request::post("http://eu.httpbin.org/post", BODY, &mut writer).unwrap(); - - println!("Status: {} {}", res.status_code(), res.reason()); - println!("Headers {}", res.headers()); - println!("{}", String::from_utf8_lossy(&writer)); -} -``` diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/server.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/server.md index 8ec06db7..93947847 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/server.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/http_service/server.md @@ -6,29 +6,33 @@ sidebar_position: 2 For WasmEdge to become a cloud-native runtime for microservices, it needs to support HTTP servers. By its very nature, the HTTP server is always asynchronous (non-blocking -- so that it can handle concurrent requests). This chapter will cover HTTP servers using popular Rust APIs. -- [The warp API](#the-warp-api) +- [The axum API](#the-warp-api) - [The hyper API](#the-hyper-api) :::note -Before we start, ensure [you have Rust and WasmEdge installed](../setup.md). +Before we start, [you need to have Rust and WasmEdge installed](../setup.md). +Make sure that you read the [special notes on networking apps](../setup#special-notes) especially if you are compiling Rust programs on a Mac. ::: -## The warp API +## The axum API -Use the warp API to create an asynchronous HTTP server. Build and run [the example](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server-warp/) in WasmEdge as follows. +The [axum](https://github.com/tokio-rs/axum) crate is the most popular HTTP server framework in the Rust Tokio ecosystem. +It is also the web framework for many popular services such as the [flows.network](https://flows.network) serverless platform for workflow functions. + +Use the axum API to create an asynchronous HTTP server. Build and run [the example](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server-axum/) in WasmEdge as follows. ```bash git clone https://github.com/WasmEdge/wasmedge_hyper_demo -cd wasmedge_hyper_demo/server-warp +cd wasmedge_hyper_demo/server-axum # Build the Rust code -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Use the AoT compiler for better performance -wasmedge compile target/wasm32-wasi/release/wasmedge_warp_server.wasm wasmedge_warp_server.wasm +wasmedge compile target/wasm32-wasi/release/wasmedge_axum_server.wasm wasmedge_axum_server.wasm # Run the example -wasmedge wasmedge_warp_server.wasm +wasmedge wasmedge_axum_server.wasm ``` Then from another terminal, you can request the server. The HTTP server echoes the request data and sends back the response. @@ -38,47 +42,68 @@ $ curl http://localhost:8080/echo -X POST -d "WasmEdge" WasmEdge ``` -In your Rust application, import the WasmEdge-adapted `warp_wasi` crate, which uses a special version of single-threaded Tokio adapted for WebAssembly. Just add the following lines to your `Cargo.toml`. +In your Rust application, you will apply a few patches developed by the WasmEdge community to replace +POSIX sockets with WasmEdge sockets in standard libraries. With those patches, you can then +use the official `tokio` and `axum` crates. + +``` +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" } -```toml [dependencies] -tokio_wasi = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]} -warp_wasi = "0.3" +axum = "0.6" +bytes = "1" +futures-util = "0.3.30" +tokio = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]} ``` -The [Rust example code](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server-warp/src/main.rs) below shows an HTTP server that echoes back any incoming request. +The [Rust example code](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server-axum/src/main.rs) below shows an HTTP server that responds to incoming requests for the `/` and `/echo` URL endpoints. ```rust #[tokio::main(flavor = "current_thread")] async fn main() { - // GET / - let help = warp::get() - .and(warp::path::end()) - .map(|| "Try POSTing data to /echo such as: `curl localhost:8080/echo -XPOST -d 'hello world'`\n"); - - // POST /echo - let echo = warp::post() - .and(warp::path("echo")) - .and(warp::body::bytes()) - .map(|body_bytes: bytes::Bytes| { - format!("{}\n", std::str::from_utf8(body_bytes.as_ref()).unwrap()) - }); - - let routes = help.or(echo); - warp::serve(routes).run(([0, 0, 0, 0], 8080)).await + // build our application with a route + let app = Router::new() + .route("/", get(help)) + .route("/echo", post(echo)); + + // run it + let addr = "0.0.0.0:8080"; + let tcp_listener = TcpListener::bind(addr).await.unwrap(); + println!("listening on {}", addr); + axum::Server::from_tcp(tcp_listener.into_std().unwrap()) + .unwrap() + .serve(app.into_make_service()) + .await + .unwrap(); +} +``` + +The `echo()` function is called when a `POST` request is received at `/echo`. The function receives and processes +the request body and returns bytes that are sent back as the response message. + +```rust +async fn echo(mut stream: BodyStream) -> Bytes { + if let Some(Ok(s)) = stream.next().await { + s + } else { + Bytes::new() + } } ``` ## The hyper API -The `warp` crate is convenient to use. But oftentimes, developers need access to lower-level APIs. The `hyper` crate is an excellent HTTP library for that. Build and run [the example](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server/) in WasmEdge as follows. +The `hyper` crate is an excellent library for building HTTP servers using customizable low level APIs. Build and run [the example](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server/) in WasmEdge as follows. ```bash git clone https://github.com/WasmEdge/wasmedge_hyper_demo cd wasmedge_hyper_demo/server # Build the Rust code -cargo build --target wasm32-wasi --release +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release # Use the AoT compiler to get better performance wasmedge compile target/wasm32-wasi/release/wasmedge_hyper_server.wasm wasmedge_hyper_server.wasm @@ -93,12 +118,17 @@ $ curl http://localhost:8080/echo -X POST -d "WasmEdge" WasmEdge ``` -In your Rust application, import the WasmEdge adapted `hyper_wasi` crate, which uses a special version of single threaded Tokio that is adapted for WebAssembly. Just add the following lines to your `Cargo.toml`. +In your Rust application, import the [hyper](https://crates.io/crates/hyper) and [tokio](https://crates.io/crates/tokio) crates, as well as the WasmEdge patches. Just add the following lines to your `Cargo.toml`. ```toml +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } +hyper = { git = "https://github.com/second-state/wasi_hyper.git", branch = "v0.14.x" } + [dependencies] -tokio_wasi = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]} -hyper_wasi = "0.15.0" +hyper = { version = "0.14", features = ["full"]} +tokio = { version = "1", features = ["rt", "macros", "net", "time", "io-util"]} ``` The [Rust example code](https://github.com/WasmEdge/wasmedge_hyper_demo/blob/main/server/src/main.rs) below shows an HTTP server that echoes back any incoming request. diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/setup.md b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/setup.md index d40bb0bd..2047432c 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/setup.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/develop/rust/setup.md @@ -32,4 +32,46 @@ To build a WASM file running in server-side WebAssembly like WasmEdge, we need t rustup target add wasm32-wasi ``` -That's it. Go to the following chapters to build and compile Rust programs in action. +## Special notes for networking apps + +### Tokio support + +WasmEdge supports async networking APIs provided by [Tokio](https://tokio.rs/) and related crates. If you have tokio in your `Cargo.toml`, you +need to add a few config flags to help the Rust compiler choose the correct feature branches in the library source code. Here is an example of `cargo build` command for +compiling a tokio app to Wasm. + +``` +RUSTFLAGS="--cfg wasmedge --cfg tokio_unstable" cargo build --target wasm32-wasi --release +``` + +Alternatively, you could add these lines to the `.cargo/config.toml` file. + +``` +[build] +target = "wasm32-wasi" +rustflags = ["--cfg", "wasmedge", "--cfg", "tokio_unstable"] +``` + +Once you have these lines in `.cargo/config.toml`, you can simply use the regular `cargo` command. + +``` +cargo build --target wasm32-wasi --release +``` + +### TLS on MacOS + +The standard `cargo` toolchain can support the [Rust TLS](https://github.com/rustls/rustls) library on Linux. However, +on MacOS, you need a special version of the Clang tool, released from the official [wasi-sdk](https://github.com/WebAssembly/wasi-sdk), in order to support TLS libraries. + +> When you compile Rust TLS source code to Wasm on Linux, the result Wasm file is cross-platform and can run correctly on any platform with WasmEdge installed. This section is only applicable when you need to **compile** Rust TLS source code on MacOS. + +[Download the latest wasi-sdk release](https://github.com/WebAssembly/wasi-sdk/releases) for your platform and +expand it into a directory. Point the `WASI_SDK_PATH` variable to this directory and export a `CC` variable for the default Clang. + +``` +export WASI_SDK_PATH /path/to/wasi-sdk-22.0 +export CC="${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot" +``` + +That's it. Now you can use the `cargo` tools on MacOS to compile tokio libraries with `rust-tls` feature turned on. +