| 1 | import * as Cloudflare from "@/Cloudflare"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Redacted from "effect/Redacted"; |
| 4 | import * as HttpClientRequest from "effect/http/HttpClientRequest"; |
| 5 | import { HttpServerRequest } from "effect/http/HttpServerRequest"; |
| 6 | |
| 7 | /** |
| 8 | * A sibling resource whose output is threaded into the container's `env`, |
| 9 | * proving that a pre-built image (no Effect bundling, no `.make()`) receives |
| 10 | * environment variables that resolve from other resources in the stack. |
| 11 | */ |
| 12 | export const EnvBucket = Cloudflare.R2.Bucket("RemoteContainerEnvBucket", { |
| 13 | forceDestroy: true, |
| 14 | }); |
| 15 | |
| 16 | /** Plain-text env value the container is expected to echo back verbatim. */ |
| 17 | export const DEMO_PLAIN = "hello-from-env"; |
| 18 | /** `Redacted` env value — encrypted in state, plain inside the container. */ |
| 19 | export const DEMO_SECRET = "sh-hh-its-a-secret"; |
| 20 | |
| 21 | export class RemoteContainer extends Cloudflare.Container<RemoteContainer>()( |
| 22 | "RemoteContainer", |
| 23 | Effect.gen(function* () { |
| 24 | const bucket = yield* EnvBucket; |
| 25 | return { |
| 26 | image: "mendhak/http-https-echo:latest", |
| 27 | vcpu: 1, |
| 28 | memoryMib: 4096, |
| 29 | observability: { logs: { enabled: true } }, |
| 30 | env: { |
| 31 | // Tells the echo image to include `process.env` in its JSON response. |
| 32 | ECHO_INCLUDE_ENV_VARS: "1", |
| 33 | DEMO_PLAIN, |
| 34 | DEMO_SECRET: Redacted.make(DEMO_SECRET), |
| 35 | DEMO_BUCKET: bucket.bucketName, |
| 36 | }, |
| 37 | }; |
| 38 | }), |
| 39 | ) {} |
| 40 | |
| 41 | /** |
| 42 | * Durable Object that binds and starts the {@link RemoteContainer} and |
| 43 | * proxies an HTTP request to the echo server running on port 8080 inside it. |
| 44 | */ |
| 45 | export class RemoteContainerObject extends Cloudflare.DurableObject<RemoteContainerObject>()( |
| 46 | "RemoteContainerObject", |
| 47 | Effect.gen(function* () { |
| 48 | const container = yield* RemoteContainer; |
| 49 | |
| 50 | return Effect.gen(function* () { |
| 51 | const { fetch } = yield* container.getTcpPort(8080); |
| 52 | |
| 53 | return { |
| 54 | hello: () => |
| 55 | Effect.gen(function* () { |
| 56 | const response = yield* fetch( |
| 57 | HttpClientRequest.get("http://container/"), |
| 58 | ); |
| 59 | return yield* response.text; |
| 60 | }), |
| 61 | // The proxy pattern from #1334: forward the incoming request to the |
| 62 | // container verbatim. In production the incoming web Request carries |
| 63 | // an https:// URL, which workerd's container ports reject — the |
| 64 | // runtime must downgrade the scheme on the container hop. |
| 65 | fetch: Effect.gen(function* () { |
| 66 | const request = yield* HttpServerRequest; |
| 67 | return yield* fetch(request); |
| 68 | }), |
| 69 | }; |
| 70 | }); |
| 71 | }).pipe( |
| 72 | Effect.provide( |
| 73 | Cloudflare.Containers.layer(RemoteContainer, { |
| 74 | enableInternet: true, |
| 75 | }), |
| 76 | ), |
| 77 | ), |
| 78 | ) {} |