mirror of
https://github.com/docker/awesome-compose.git
synced 2025-04-26 10:43:35 +02:00
Feat: add Docker+wasm examples (#309)
* Add a Docker+wasm sample application featuring a WasmEdge-based microservice, a MySQL database and an Nginx web server for frontend UI files. Signed-off-by: Michael Yuan <michael@secondstate.io> * Add a logo to indicate Docker+wasm compatibility. Add project descriptions to README. Signed-off-by: Michael Yuan <michael@secondstate.io> * Add the example for WasmEdge + Kafka / Redpanda + MySQL application to take messages from a queue and save into a database table. Signed-off-by: Michael Yuan <michael@secondstate.io> * Add a SVG icon to indicate Docker + Wasm req Signed-off-by: Michael Yuan <michael@michaelyuan.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update the docker compose files for the new Docker Desktop release Signed-off-by: Michael Yuan <michael@secondstate.io> * Use the correct platform to be compatible with Docker Desktop 4.15 Signed-off-by: Michael Yuan <michael@secondstate.io> * Update README.md Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-kafka-mysql/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-kafka-mysql/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-kafka-mysql/etl/Dockerfile Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Update wasmedge-mysql-nginx/README.md Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> * Change the Nginx port to the default non-privileged 8090 Signed-off-by: Michael Yuan <michael@secondstate.io> * My apologies. Need to correct the syntax for the Nginx port 8090. Signed-off-by: Michael Yuan <michael@secondstate.io> * Remove commented lines Signed-off-by: Michael Yuan <michael@secondstate.io> * Change wasi/wasm32 to wasi/wasm to conform with the latest spec Signed-off-by: Michael Yuan <michael@secondstate.io> * Update README.md Co-authored-by: Michael Irwin <mikesir87@gmail.com> Signed-off-by: Michael Yuan <michael@michaelyuan.com> Signed-off-by: Michael Yuan <michael@secondstate.io> Signed-off-by: Michael Yuan <michael@michaelyuan.com> Co-authored-by: David Karlsson <35727626+dvdksn@users.noreply.github.com> Co-authored-by: Michael Irwin <mikesir87@gmail.com>
This commit is contained in:
parent
6f15838d24
commit
e6b1d2755f
20 changed files with 1057 additions and 0 deletions
13
wasmedge-mysql-nginx/backend/Cargo.toml
Normal file
13
wasmedge-mysql-nginx/backend/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "order_demo_service"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
url = "2.3"
|
||||
mysql_async_wasi = "0.30"
|
||||
hyper_wasi = { version = "0.15", features = ["full"] }
|
||||
tokio_wasi = { version = "1", features = ["io-util", "fs", "net", "time", "rt", "macros"] }
|
29
wasmedge-mysql-nginx/backend/Dockerfile
Normal file
29
wasmedge-mysql-nginx/backend/Dockerfile
Normal file
|
@ -0,0 +1,29 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM --platform=$BUILDPLATFORM rust:1.64 AS buildbase
|
||||
WORKDIR /src
|
||||
RUN <<EOT bash
|
||||
set -ex
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
git \
|
||||
clang
|
||||
rustup target add wasm32-wasi
|
||||
EOT
|
||||
# This line installs WasmEdge including the AOT compiler
|
||||
RUN curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
|
||||
|
||||
FROM buildbase AS build
|
||||
COPY Cargo.toml .
|
||||
COPY src ./src
|
||||
# Build the Wasm binary
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/git/db \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry/cache \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry/index \
|
||||
cargo build --target wasm32-wasi --release
|
||||
# This line builds the AOT Wasm binary
|
||||
RUN /root/.wasmedge/bin/wasmedgec target/wasm32-wasi/release/order_demo_service.wasm order_demo_service.wasm
|
||||
|
||||
FROM scratch
|
||||
ENTRYPOINT [ "order_demo_service.wasm" ]
|
||||
COPY --link --from=build /src/order_demo_service.wasm /order_demo_service.wasm
|
237
wasmedge-mysql-nginx/backend/src/main.rs
Normal file
237
wasmedge-mysql-nginx/backend/src/main.rs
Normal file
|
@ -0,0 +1,237 @@
|
|||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Method, Request, Response, StatusCode, Server};
|
||||
pub use mysql_async::prelude::*;
|
||||
pub use mysql_async::*;
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::result::Result;
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn get_url() -> String {
|
||||
if let Ok(url) = std::env::var("DATABASE_URL") {
|
||||
let opts = Opts::from_url(&url).expect("DATABASE_URL invalid");
|
||||
if opts
|
||||
.db_name()
|
||||
.expect("a database name is required")
|
||||
.is_empty()
|
||||
{
|
||||
panic!("database name is empty");
|
||||
}
|
||||
url
|
||||
} else {
|
||||
"mysql://root:pass@127.0.0.1:3306/mysql".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Order {
|
||||
order_id: i32,
|
||||
product_id: i32,
|
||||
quantity: i32,
|
||||
amount: f32,
|
||||
shipping: f32,
|
||||
tax: f32,
|
||||
shipping_address: String,
|
||||
}
|
||||
|
||||
impl Order {
|
||||
fn new(
|
||||
order_id: i32,
|
||||
product_id: i32,
|
||||
quantity: i32,
|
||||
amount: f32,
|
||||
shipping: f32,
|
||||
tax: f32,
|
||||
shipping_address: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
order_id,
|
||||
product_id,
|
||||
quantity,
|
||||
amount,
|
||||
shipping,
|
||||
tax,
|
||||
shipping_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(req: Request<Body>, pool: Pool) -> Result<Response<Body>, anyhow::Error> {
|
||||
match (req.method(), req.uri().path()) {
|
||||
(&Method::GET, "/") => Ok(Response::new(Body::from(
|
||||
"The valid endpoints are /init /create_order /create_orders /update_order /orders /delete_order",
|
||||
))),
|
||||
|
||||
// Simply echo the body back to the client.
|
||||
(&Method::POST, "/echo") => Ok(Response::new(req.into_body())),
|
||||
|
||||
// CORS OPTIONS
|
||||
(&Method::OPTIONS, "/init") => Ok(response_build(&String::from(""))),
|
||||
(&Method::OPTIONS, "/create_order") => Ok(response_build(&String::from(""))),
|
||||
(&Method::OPTIONS, "/create_orders") => Ok(response_build(&String::from(""))),
|
||||
(&Method::OPTIONS, "/update_order") => Ok(response_build(&String::from(""))),
|
||||
(&Method::OPTIONS, "/delete_order") => Ok(response_build(&String::from(""))),
|
||||
(&Method::OPTIONS, "/orders") => Ok(response_build(&String::from(""))),
|
||||
|
||||
(&Method::GET, "/init") => {
|
||||
let mut conn = pool.get_conn().await.unwrap();
|
||||
"DROP TABLE IF EXISTS orders;".ignore(&mut conn).await?;
|
||||
"CREATE TABLE orders (order_id INT, product_id INT, quantity INT, amount FLOAT, shipping FLOAT, tax FLOAT, shipping_address VARCHAR(20));".ignore(&mut conn).await?;
|
||||
drop(conn);
|
||||
Ok(response_build("{\"status\":true}"))
|
||||
}
|
||||
|
||||
(&Method::POST, "/create_order") => {
|
||||
let mut conn = pool.get_conn().await.unwrap();
|
||||
|
||||
let byte_stream = hyper::body::to_bytes(req).await?;
|
||||
let order: Order = serde_json::from_slice(&byte_stream).unwrap();
|
||||
|
||||
"INSERT INTO orders (order_id, product_id, quantity, amount, shipping, tax, shipping_address) VALUES (:order_id, :product_id, :quantity, :amount, :shipping, :tax, :shipping_address)"
|
||||
.with(params! {
|
||||
"order_id" => order.order_id,
|
||||
"product_id" => order.product_id,
|
||||
"quantity" => order.quantity,
|
||||
"amount" => order.amount,
|
||||
"shipping" => order.shipping,
|
||||
"tax" => order.tax,
|
||||
"shipping_address" => &order.shipping_address,
|
||||
})
|
||||
.ignore(&mut conn)
|
||||
.await?;
|
||||
|
||||
drop(conn);
|
||||
Ok(response_build("{\"status\":true}"))
|
||||
}
|
||||
|
||||
(&Method::POST, "/create_orders") => {
|
||||
let mut conn = pool.get_conn().await.unwrap();
|
||||
|
||||
let byte_stream = hyper::body::to_bytes(req).await?;
|
||||
let orders: Vec<Order> = serde_json::from_slice(&byte_stream).unwrap();
|
||||
|
||||
"INSERT INTO orders (order_id, product_id, quantity, amount, shipping, tax, shipping_address) VALUES (:order_id, :product_id, :quantity, :amount, :shipping, :tax, :shipping_address)"
|
||||
.with(orders.iter().map(|order| {
|
||||
params! {
|
||||
"order_id" => order.order_id,
|
||||
"product_id" => order.product_id,
|
||||
"quantity" => order.quantity,
|
||||
"amount" => order.amount,
|
||||
"shipping" => order.shipping,
|
||||
"tax" => order.tax,
|
||||
"shipping_address" => &order.shipping_address,
|
||||
}
|
||||
}))
|
||||
.batch(&mut conn)
|
||||
.await?;
|
||||
|
||||
drop(conn);
|
||||
Ok(response_build("{\"status\":true}"))
|
||||
}
|
||||
|
||||
(&Method::POST, "/update_order") => {
|
||||
let mut conn = pool.get_conn().await.unwrap();
|
||||
|
||||
let byte_stream = hyper::body::to_bytes(req).await?;
|
||||
let order: Order = serde_json::from_slice(&byte_stream).unwrap();
|
||||
|
||||
"UPDATE orders SET product_id=:product_id, quantity=:quantity, amount=:amount, shipping=:shipping, tax=:tax, shipping_address=:shipping_address WHERE order_id=:order_id"
|
||||
.with(params! {
|
||||
"product_id" => order.product_id,
|
||||
"quantity" => order.quantity,
|
||||
"amount" => order.amount,
|
||||
"shipping" => order.shipping,
|
||||
"tax" => order.tax,
|
||||
"shipping_address" => &order.shipping_address,
|
||||
"order_id" => order.order_id,
|
||||
})
|
||||
.ignore(&mut conn)
|
||||
.await?;
|
||||
|
||||
drop(conn);
|
||||
Ok(response_build("{\"status\":true}"))
|
||||
}
|
||||
|
||||
(&Method::GET, "/orders") => {
|
||||
let mut conn = pool.get_conn().await.unwrap();
|
||||
|
||||
let orders = "SELECT * FROM orders"
|
||||
.with(())
|
||||
.map(&mut conn, |(order_id, product_id, quantity, amount, shipping, tax, shipping_address)| {
|
||||
Order::new(
|
||||
order_id,
|
||||
product_id,
|
||||
quantity,
|
||||
amount,
|
||||
shipping,
|
||||
tax,
|
||||
shipping_address,
|
||||
)},
|
||||
).await?;
|
||||
|
||||
drop(conn);
|
||||
Ok(response_build(serde_json::to_string(&orders)?.as_str()))
|
||||
}
|
||||
|
||||
(&Method::GET, "/delete_order") => {
|
||||
let mut conn = pool.get_conn().await.unwrap();
|
||||
|
||||
let params: HashMap<String, String> = req.uri().query().map(|v| {
|
||||
url::form_urlencoded::parse(v.as_bytes()).into_owned().collect()
|
||||
}).unwrap_or_else(HashMap::new);
|
||||
let order_id = params.get("id");
|
||||
|
||||
"DELETE FROM orders WHERE order_id=:order_id"
|
||||
.with(params! { "order_id" => order_id, })
|
||||
.ignore(&mut conn)
|
||||
.await?;
|
||||
|
||||
drop(conn);
|
||||
Ok(response_build("{\"status\":true}"))
|
||||
}
|
||||
|
||||
// Return the 404 Not Found for other routes.
|
||||
_ => {
|
||||
let mut not_found = Response::default();
|
||||
*not_found.status_mut() = StatusCode::NOT_FOUND;
|
||||
Ok(not_found)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CORS headers
|
||||
fn response_build(body: &str) -> Response<Body> {
|
||||
Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
.header("Access-Control-Allow-Headers", "api,Keep-Alive,User-Agent,Content-Type")
|
||||
.body(Body::from(body.to_owned()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let opts = Opts::from_url(&*get_url()).unwrap();
|
||||
let builder = OptsBuilder::from_opts(opts);
|
||||
// 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 addr = SocketAddr::from(([0, 0, 0, 0], 8080));
|
||||
let make_svc = make_service_fn(|_| {
|
||||
let pool = pool.clone();
|
||||
async move {
|
||||
Ok::<_, Infallible>(service_fn(move |req| {
|
||||
let pool = pool.clone();
|
||||
handle_request(req, pool)
|
||||
}))
|
||||
}
|
||||
});
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue