From 4e3763ecd2e99821d55d736c678a81883acbd089 Mon Sep 17 00:00:00 2001 From: hr567 Date: Sat, 6 Sep 2025 09:26:10 +0000 Subject: [PATCH 1/3] Change wasm-bindgen modules to https url Signed-off-by: hr567 --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 370ae3ac..1f6decbe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "wasm-bindgen"] path = wasm-bindgen - url = git@github.com:wasm-bindgen/wasm-bindgen + url = https://github.com/wasm-bindgen/wasm-bindgen From 6ab8807c1374153d197ab1913b225d8c53f83ef0 Mon Sep 17 00:00:00 2001 From: hr567 Date: Tue, 2 Sep 2025 03:37:21 +0000 Subject: [PATCH 2/3] Add Containers bindings Signed-off-by: hr567 --- worker-sys/src/types/durable_object.rs | 2 + .../src/types/durable_object/container.rs | 62 ++++++++++ worker-sys/src/types/durable_object/state.rs | 7 +- worker/src/container.rs | 114 ++++++++++++++++++ worker/src/durable.rs | 5 + worker/src/lib.rs | 2 + 6 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 worker-sys/src/types/durable_object/container.rs create mode 100644 worker/src/container.rs diff --git a/worker-sys/src/types/durable_object.rs b/worker-sys/src/types/durable_object.rs index 0e48fdfc..c1d3b270 100644 --- a/worker-sys/src/types/durable_object.rs +++ b/worker-sys/src/types/durable_object.rs @@ -1,5 +1,6 @@ use wasm_bindgen::prelude::*; +mod container; mod id; mod namespace; mod sql_storage; @@ -7,6 +8,7 @@ mod state; mod storage; mod transaction; +pub use container::*; pub use id::*; pub use namespace::*; pub use sql_storage::*; diff --git a/worker-sys/src/types/durable_object/container.rs b/worker-sys/src/types/durable_object/container.rs new file mode 100644 index 00000000..0dea9b5d --- /dev/null +++ b/worker-sys/src/types/durable_object/container.rs @@ -0,0 +1,62 @@ +use js_sys::{Object, Promise}; +use wasm_bindgen::prelude::*; + +use crate::Fetcher; + +/// ```ts +/// interface Container { +/// get running(): boolean; +/// start(options?: ContainerStartupOptions): void; +/// monitor(): Promise; +/// destroy(error?: any): Promise; +/// signal(signo: number): void; +/// getTcpPort(port: number): Fetcher; +/// } +/// +/// interface ContainerStartupOptions { +/// entrypoint?: string[]; +/// enableInternet: boolean; +/// env?: Record; +/// } +/// ``` + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends=js_sys::Object)] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type Container; + + #[wasm_bindgen(method, getter)] + pub fn running(this: &Container) -> bool; + + #[wasm_bindgen(method)] + pub fn start(this: &Container, options: Option<&ContainerStartupOptions>); + + #[wasm_bindgen(method)] + pub fn monitor(this: &Container) -> Promise; + + #[wasm_bindgen(method)] + pub fn destroy(this: &Container, error: &JsValue) -> Promise; + + #[wasm_bindgen(method)] + pub fn signal(this: &Container, signo: i32); + + #[wasm_bindgen(method, catch, js_name=getTcpPort)] + pub fn get_tcp_port(this: &Container, port: u16) -> Result; +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends=js_sys::Object)] + #[derive(Debug, Clone, PartialEq, Eq)] + pub type ContainerStartupOptions; + + #[wasm_bindgen(method, getter)] + pub fn entrypoint(this: &ContainerStartupOptions) -> Option>; + + #[wasm_bindgen(method, getter, js_name=enableInternet)] + pub fn enable_internet(this: &ContainerStartupOptions) -> bool; + + #[wasm_bindgen(method, catch, getter)] + pub fn env(this: &ContainerStartupOptions) -> Result; +} diff --git a/worker-sys/src/types/durable_object/state.rs b/worker-sys/src/types/durable_object/state.rs index 8c343261..32e4dbf7 100644 --- a/worker-sys/src/types/durable_object/state.rs +++ b/worker-sys/src/types/durable_object/state.rs @@ -1,6 +1,8 @@ use wasm_bindgen::prelude::*; -use crate::types::{DurableObjectId, DurableObjectStorage, WebSocketRequestResponsePair}; +use crate::types::{ + Container, DurableObjectId, DurableObjectStorage, WebSocketRequestResponsePair, +}; #[wasm_bindgen] extern "C" { @@ -13,6 +15,9 @@ extern "C" { #[wasm_bindgen(method, catch, getter)] pub fn storage(this: &DurableObjectState) -> Result; + #[wasm_bindgen(method, getter)] + pub fn container(this: &DurableObjectState) -> Option; + #[wasm_bindgen(method, catch, js_name=waitUntil)] pub fn wait_until(this: &DurableObjectState, promise: &js_sys::Promise) -> Result<(), JsValue>; diff --git a/worker/src/container.rs b/worker/src/container.rs new file mode 100644 index 00000000..33f9479c --- /dev/null +++ b/worker/src/container.rs @@ -0,0 +1,114 @@ +use wasm_bindgen::{JsCast, JsValue}; + +use crate::{Fetcher, Result}; + +#[derive(Debug)] +pub struct Container { + pub(super) inner: worker_sys::Container, +} + +impl Container { + pub fn running(&self) -> bool { + self.inner.running() + } + + pub fn start(&self, options: Option) { + self.inner.start(options.as_ref().map(|o| &o.inner)) + } + + pub fn get_tcp_port(&self, port: u16) -> Result { + self.inner + .get_tcp_port(port) + .map(|f| f.into()) + .map_err(|e| e.into()) + } +} + +unsafe impl Sync for Container {} +unsafe impl Send for Container {} + +impl From for Container { + fn from(inner: worker_sys::Container) -> Self { + Self { inner } + } +} + +impl AsRef for Container { + fn as_ref(&self) -> &JsValue { + &self.inner + } +} + +impl From for JsValue { + fn from(database: Container) -> Self { + JsValue::from(database.inner) + } +} + +impl JsCast for Container { + fn instanceof(val: &JsValue) -> bool { + val.is_instance_of::() + } + + fn unchecked_from_js(val: JsValue) -> Self { + Self { inner: val.into() } + } + + fn unchecked_from_js_ref(val: &JsValue) -> &Self { + unsafe { &*(val as *const JsValue as *const Self) } + } +} + +#[derive(Debug)] +pub struct ContainerStartupOptions { + inner: worker_sys::ContainerStartupOptions, +} + +impl ContainerStartupOptions { + pub fn entrypoint(&self) -> Option> { + self.inner.entrypoint() + } + + pub fn enable_internet(&self) -> bool { + self.inner.enable_internet() + } + + pub fn env(&self) -> Result { + Ok(self.inner.env()?) + } +} + +unsafe impl Sync for ContainerStartupOptions {} +unsafe impl Send for ContainerStartupOptions {} + +impl From for ContainerStartupOptions { + fn from(inner: worker_sys::ContainerStartupOptions) -> Self { + Self { inner } + } +} + +impl AsRef for ContainerStartupOptions { + fn as_ref(&self) -> &JsValue { + &self.inner + } +} + +impl From for JsValue { + fn from(database: ContainerStartupOptions) -> Self { + JsValue::from(database.inner) + } +} + +impl JsCast for ContainerStartupOptions { + fn instanceof(val: &JsValue) -> bool { + val.is_instance_of::() + } + + fn unchecked_from_js(val: JsValue) -> Self { + Self { inner: val.into() } + } + + fn unchecked_from_js_ref(val: &JsValue) -> &Self { + unsafe { &*(val as *const JsValue as *const Self) } + } +} diff --git a/worker/src/durable.rs b/worker/src/durable.rs index 862e8bd3..6c86456a 100644 --- a/worker/src/durable.rs +++ b/worker/src/durable.rs @@ -13,6 +13,7 @@ use std::{fmt::Display, ops::Deref, time::Duration}; use crate::{ + container::Container, date::Date, env::{Env, EnvBinding}, error::Error, @@ -255,6 +256,10 @@ impl State { } } + pub fn container(&self) -> Option { + self.inner.container().map(|inner| Container { inner }) + } + pub fn wait_until(&self, future: F) where F: Future + 'static, diff --git a/worker/src/lib.rs b/worker/src/lib.rs index 0537e5e5..af0a27a2 100644 --- a/worker/src/lib.rs +++ b/worker/src/lib.rs @@ -167,6 +167,7 @@ pub use crate::abort::*; pub use crate::ai::*; pub use crate::analytics_engine::*; pub use crate::cache::{Cache, CacheDeletionOutcome, CacheKey}; +pub use crate::container::*; pub use crate::context::Context; pub use crate::cors::Cors; #[cfg(feature = "d1")] @@ -203,6 +204,7 @@ mod ai; mod analytics_engine; mod cache; mod cf; +mod container; mod context; mod cors; pub mod crypto; From c3756fbd49a70a8d4c426a76ca6bb40a52dec56a Mon Sep 17 00:00:00 2001 From: hr567 Date: Fri, 5 Sep 2025 10:13:38 +0000 Subject: [PATCH 3/3] Add container test in worker-sandbox Signed-off-by: hr567 --- Cargo.lock | 88 +++++++++++++++++++--- Cargo.toml | 3 +- worker-sandbox/container-echo/Cargo.toml | 8 ++ worker-sandbox/container-echo/Dockerfile | 10 +++ worker-sandbox/container-echo/README.md | 2 + worker-sandbox/container-echo/src/main.rs | 34 +++++++++ worker-sandbox/src/container.rs | 90 +++++++++++++++++++++++ worker-sandbox/src/lib.rs | 1 + worker-sandbox/src/router.rs | 6 +- worker-sandbox/tests/container.spec.ts | 40 ++++++++++ worker-sandbox/tests/mf.ts | 1 + worker-sandbox/wrangler.toml | 8 +- 12 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 worker-sandbox/container-echo/Cargo.toml create mode 100644 worker-sandbox/container-echo/Dockerfile create mode 100644 worker-sandbox/container-echo/README.md create mode 100644 worker-sandbox/container-echo/src/main.rs create mode 100644 worker-sandbox/src/container.rs create mode 100644 worker-sandbox/tests/container.spec.ts diff --git a/Cargo.lock b/Cargo.lock index a75c296b..67845b23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,11 +159,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", + "base64", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", + "hyper", + "hyper-util", "itoa", "matchit 0.8.4", "memchr", @@ -172,10 +176,17 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", "sync_wrapper", + "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -195,6 +206,7 @@ dependencies = [ "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -365,9 +377,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.35" +version = "1.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590f9024a68a8c40351881787f1934dc11afd69090f5edb6831464694d836ea3" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ "find-msvc-tools", "jobserver", @@ -490,6 +502,14 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "container-echo" +version = "0.1.0" +dependencies = [ + "axum", + "tokio", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -825,9 +845,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e178e4fba8a2726903f6ba98a6d221e76f9c12c650d5dc0e6afdc50677b49650" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" [[package]] name = "flate2" @@ -978,7 +998,7 @@ dependencies = [ "js-sys", "libc", "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "wasi 0.14.4+wasi-0.2.4", "wasm-bindgen", ] @@ -1125,6 +1145,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.7.0" @@ -1139,6 +1165,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -2423,6 +2450,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -2856,6 +2893,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.2", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -2962,6 +3011,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -3000,6 +3050,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3095,6 +3146,23 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.16", + "utf-8", +] + [[package]] name = "tungstenite" version = "0.27.0" @@ -3346,9 +3414,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.3+wasi-0.2.4" +version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ "wit-bindgen", ] @@ -3890,9 +3958,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" [[package]] name = "wit-bindgen" -version = "0.45.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "wit-parser" @@ -4046,7 +4114,7 @@ dependencies = [ "tokio", "tokio-stream", "tower-service", - "tungstenite", + "tungstenite 0.27.0", "uuid", "wasm-bindgen", "wasm-bindgen-test", diff --git a/Cargo.toml b/Cargo.toml index b1e25391..49ed0d10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "worker-sys", "worker-kv", "examples/*", + "worker-sandbox/container-echo", ] exclude = ["examples/coredump", "examples/axum", "templates/*", "wasm-bindgen"] resolver = "2" @@ -38,7 +39,7 @@ worker-macros = { path = "worker-macros", version = "0.6.1", features = ["queue" worker-sys = { path = "worker-sys", version = "0.6.1", features = ["d1", "queue"] } [profile.release] -# rustc supports two "optimize for size" levels: opt-level = "s" and "z". +# rustc supports two "optimize for size" levels: opt-level = "s" and "z". # These names were inherited from clang / LLVM and are not too descriptive # but "z" is meant to give the idea that it produces smaller binaries than "s". # https://docs.rust-embedded.org/book/unsorted/speed-vs-size.html#optimize-for-size diff --git a/worker-sandbox/container-echo/Cargo.toml b/worker-sandbox/container-echo/Cargo.toml new file mode 100644 index 00000000..34b97227 --- /dev/null +++ b/worker-sandbox/container-echo/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "container-echo" +version = "0.1.0" +edition = "2024" + +[dependencies] +axum = { version = "0.8.4", features = ["ws"] } +tokio = { version = "1.47.1", features = ["rt-multi-thread", "net"] } diff --git a/worker-sandbox/container-echo/Dockerfile b/worker-sandbox/container-echo/Dockerfile new file mode 100644 index 00000000..ed0d2921 --- /dev/null +++ b/worker-sandbox/container-echo/Dockerfile @@ -0,0 +1,10 @@ +FROM rust:alpine AS builder +COPY ./ /app +WORKDIR /app +RUN apk add musl-dev +RUN cargo build --release + +FROM alpine:latest +COPY --from=builder /app/target/release/container-echo /container-echo +ENTRYPOINT ["/container-echo"] +EXPOSE 8080 diff --git a/worker-sandbox/container-echo/README.md b/worker-sandbox/container-echo/README.md new file mode 100644 index 00000000..40c0f9bc --- /dev/null +++ b/worker-sandbox/container-echo/README.md @@ -0,0 +1,2 @@ +# Echo Server +A simple echo server. diff --git a/worker-sandbox/container-echo/src/main.rs b/worker-sandbox/container-echo/src/main.rs new file mode 100644 index 00000000..b168c239 --- /dev/null +++ b/worker-sandbox/container-echo/src/main.rs @@ -0,0 +1,34 @@ +use std::io; + +use axum::{ + Router, + body::Body, + extract::ws::{WebSocket, WebSocketUpgrade}, + routing::{get, post}, +}; +use tokio::net::TcpListener; + +#[tokio::main] +async fn main() -> io::Result<()> { + let router = Router::new() + .route("/echo", post(|b: Body| async { b })) + .route( + "/ws", + get(|ws: WebSocketUpgrade| async { ws.on_upgrade(handle_ws) }), + ); + let listener = TcpListener::bind("0.0.0.0:8080").await?; + axum::serve(listener, router).await?; + Ok(()) +} + +async fn handle_ws(mut ws: WebSocket) { + while let Some(msg) = ws.recv().await { + let msg = match msg { + Ok(v) => v, + Err(_) => return, + }; + if ws.send(msg).await.is_err() { + return; + } + } +} diff --git a/worker-sandbox/src/container.rs b/worker-sandbox/src/container.rs new file mode 100644 index 00000000..7d3bb5a5 --- /dev/null +++ b/worker-sandbox/src/container.rs @@ -0,0 +1,90 @@ +use futures_util::StreamExt; +use wasm_bindgen::{throw_str, UnwrapThrowExt}; +use wasm_bindgen_futures::spawn_local; +use worker::*; + +use crate::SomeSharedData; + +#[durable_object] +pub struct EchoContainer { + state: State, +} + +impl DurableObject for EchoContainer { + fn new(state: State, _env: Env) -> Self { + Self { state } + } + + async fn fetch(&self, req: Request) -> Result { + match self.state.container() { + Some(container) => container.get_tcp_port(8080)?.fetch_request(req).await, + None => Response::error("No container", 500), + } + } +} + +const CONTAINER_NAME: &str = "my-container"; + +#[worker::send] +pub async fn handle_container( + mut req: Request, + env: Env, + _data: SomeSharedData, +) -> Result { + let namespace = env.durable_object("ECHO_CONTAINER")?; + let id = namespace.id_from_name(CONTAINER_NAME)?; + let stub = id.get_stub()?; + match req.method() { + Method::Post => { + let body = req.text().await?; + let req = Request::new_with_init( + "https://fake-host/echo", + RequestInit::new() + .with_method(Method::Post) + .with_body(Some(body.into())), + )?; + stub.fetch_with_request(req).await + } + Method::Get => { + let WebSocketPair { server, client } = WebSocketPair::new()?; + server.accept()?; + + let mut req = Request::new("https://fake-host/ws", Method::Get)?; + req.headers_mut()?.set("upgrade", "websocket")?; + + let resp = stub.fetch_with_request(req).await?; + let ws = match resp.websocket() { + Some(ws) => ws, + None => return Response::error("Expected websocket response", 500), + }; + ws.accept()?; + spawn_local(redir_websocket(ws, server)); + Response::from_websocket(client) + } + _ => Response::error("Container method not allowed", 405), + } +} + +async fn redir_websocket(dst: WebSocket, src: WebSocket) { + let mut src_events = src.events().expect_throw("could not open src events"); + let mut dst_events = dst.events().expect_throw("could not open dst events"); + + while let Some(event) = src_events.next().await { + match event.expect_throw("received error in src websocket") { + WebsocketEvent::Message(msg) => { + dst.send_with_str(msg.text().expect_throw("expect a text message from src")) + .expect_throw("failed to send to dst"); + if let Some(Ok(WebsocketEvent::Message(msg))) = dst_events.next().await { + src.send_with_str(msg.text().expect_throw("expect a text message from dst")) + .expect_throw("failed to send to src"); + } else { + throw_str("expect a text message from dst"); + } + } + WebsocketEvent::Close(reason) => { + dst.close(Some(reason.code()), Some(reason.reason())) + .expect_throw("failed to close dst websocket"); + } + } + } +} diff --git a/worker-sandbox/src/lib.rs b/worker-sandbox/src/lib.rs index 9d6ccd51..6be6d76d 100644 --- a/worker-sandbox/src/lib.rs +++ b/worker-sandbox/src/lib.rs @@ -17,6 +17,7 @@ mod analytics_engine; mod assets; mod auto_response; mod cache; +mod container; mod counter; mod d1; mod durable; diff --git a/worker-sandbox/src/router.rs b/worker-sandbox/src/router.rs index 6285a4a3..99d1532c 100644 --- a/worker-sandbox/src/router.rs +++ b/worker-sandbox/src/router.rs @@ -1,6 +1,6 @@ use crate::{ - alarm, analytics_engine, assets, auto_response, cache, counter, d1, durable, fetch, form, - js_snippets, kv, put_raw, queue, r2, request, secret_store, service, socket, sql_counter, + alarm, analytics_engine, assets, auto_response, cache, container, counter, d1, durable, fetch, + form, js_snippets, kv, put_raw, queue, r2, request, secret_store, service, socket, sql_counter, sql_iterator, user, ws, SomeSharedData, GLOBAL_STATE, }; #[cfg(feature = "http")] @@ -224,6 +224,8 @@ macro_rules! add_routes ( add_route!($obj, get, format_route!("/sql-iterator/{}", "*path"), sql_iterator::handle_sql_iterator); add_route!($obj, get, "/get-from-secret-store", secret_store::get_from_secret_store); add_route!($obj, get, "/get-from-secret-store-missing", secret_store::get_from_secret_store_missing); + add_route!($obj, post, "/container/echo", container::handle_container); + add_route!($obj, get, "/container/ws", container::handle_container); }); #[cfg(feature = "http")] diff --git a/worker-sandbox/tests/container.spec.ts b/worker-sandbox/tests/container.spec.ts new file mode 100644 index 00000000..b31a2839 --- /dev/null +++ b/worker-sandbox/tests/container.spec.ts @@ -0,0 +1,40 @@ +import { describe, test, expect } from "vitest"; +import { mf, mfUrl } from "./mf"; +import { MessageEvent } from "miniflare"; + +describe("durable", () => { + test("post-echo", async () => { + const resp = await mf.dispatchFetch(`${mfUrl}container/echo`, { + "method": "POST", + "body": "Hello container!" + }); + expect(await resp.text()).toBe("Hello container!"); + }); + + test("websocket-to-container", async () => { + const resp = await mf.dispatchFetch(`${mfUrl}container/ws`, { + headers: { + upgrade: "websocket", + }, + }); + expect(resp.webSocket).not.toBeNull(); + + const socket = resp.webSocket!; + socket.accept(); + + const messages = ["123", "223", "323", "abc"]; + + let idx = 0; + socket.addEventListener("message", function (event: MessageEvent) { + expect(event.data).toBe(messages[idx]); + idx++; + }); + + for (const msg of messages) { + socket.send(msg); + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + socket.close(); + }); +}); diff --git a/worker-sandbox/tests/mf.ts b/worker-sandbox/tests/mf.ts index 216b6d8a..7ef70ba7 100644 --- a/worker-sandbox/tests/mf.ts +++ b/worker-sandbox/tests/mf.ts @@ -61,6 +61,7 @@ const mf_instance = new Miniflare({ PUT_RAW_TEST_OBJECT: "PutRawTestObject", AUTO: "AutoResponseObject", MY_CLASS: "MyClass", + ECHO_CONTAINER: "EchoContainer", SQL_COUNTER: { className: "SqlCounter", useSQLite: true, diff --git a/worker-sandbox/wrangler.toml b/worker-sandbox/wrangler.toml index 55f4b997..fe49396b 100644 --- a/worker-sandbox/wrangler.toml +++ b/worker-sandbox/wrangler.toml @@ -30,6 +30,7 @@ bindings = [ { name = "SQL_COUNTER", class_name = "SqlCounter" }, { name = "SQL_ITERATOR", class_name = "SqlIterator" }, { name = "MY_CLASS", class_name = "MyClass" }, + { name = "ECHO_CONTAINER", class_name = "EchoContainer" }, ] [[analytics_engine_datasets]] @@ -73,9 +74,14 @@ command = "worker-build --release" [[migrations]] tag = "v1" -new_sqlite_classes = ["SqlCounter", "SqlIterator"] +new_sqlite_classes = ["SqlCounter", "SqlIterator", "EchoContainer"] [[secrets_store_secrets]] binding = "SECRETS" store_id = "SECRET_STORE" secret_name = "secret-name" + +[[containers]] +class_name = "EchoContainer" +image = "./container-echo/Dockerfile" +max_instances = 1