mirror of
https://github.com/docker/awesome-compose.git
synced 2025-04-26 10:43:35 +02:00
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>
This commit is contained in:
parent
e3ea3e9044
commit
6fc5abd81d
11 changed files with 765 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
|
256
wasmedge-mysql-nginx/backend/src/main.rs
Normal file
256
wasmedge-mysql-nginx/backend/src/main.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
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}"))
|
||||
// Ok(Response::new(Body::from("{\"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}"))
|
||||
// Ok(Response::new(Body::from("{\"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}"))
|
||||
// Ok(Response::new(Body::from("{\"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()))
|
||||
// Ok(Response::new(Body::from(serde_json::to_string(&orders)?)))
|
||||
}
|
||||
|
||||
(&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}"))
|
||||
// Ok(Response::new(Body::from("{\"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(())
|
||||
|
||||
/*
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new().serve_connection(stream, service_fn(handle_request)).await {
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue