diff --git a/samples/multiple-codebases/README.md b/samples/multiple-codebases/README.md new file mode 100644 index 00000000000..382236467ee --- /dev/null +++ b/samples/multiple-codebases/README.md @@ -0,0 +1,45 @@ +# Multiple Codebases Example + +This example runs two applications with separate Worker scripts in one workerd +process. Both applications share a listening socket. A router Worker selects an +application by the request URL's hostname and forwards the request through a +service binding. Unrecognized hostnames receive a 404 response. + +Use this pattern for codebases you trust, such as multiple applications belonging +to the same organization. Host-based routing does not provide the security +isolation needed to run arbitrary, potentially malicious tenant code. + +## Run + +From this directory, using a workerd binary: + +```sh +workerd serve config.capnp +``` + +Or from the repository root, using Bazel: + +```sh +bazel run //src/workerd/server:workerd -- serve "$PWD/samples/multiple-codebases/config.capnp" +``` + +## Try Both Applications + +The example listens on `127.0.0.1:8080`. Set the `Host` header to select an +application without configuring local DNS: + +```sh +curl -H 'Host: one.example.test' 'http://127.0.0.1:8080/hello?name=one' +# App one: http://one.example.test/hello?name=one + +curl -H 'Host: two.example.test' 'http://127.0.0.1:8080/hello?name=two' +# App two: http://two.example.test/hello?name=two + +curl -i -H 'Host: unknown.example.test' http://127.0.0.1:8080/ +# HTTP/1.1 404 Not Found +``` + +Each application's service can have its own modules, bindings, and compatibility +date. To add an application, configure its service, give the router a binding to +that service, and add its hostname to the router. The original request, including +its path, query string, method, and body, is passed to the selected application. diff --git a/samples/multiple-codebases/app-one.js b/samples/multiple-codebases/app-one.js new file mode 100644 index 00000000000..6d3e6c58b84 --- /dev/null +++ b/samples/multiple-codebases/app-one.js @@ -0,0 +1,9 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export default { + fetch(request) { + return new Response(`App one: ${request.url}\n`); + }, +}; diff --git a/samples/multiple-codebases/app-two.js b/samples/multiple-codebases/app-two.js new file mode 100644 index 00000000000..e8f106adbe0 --- /dev/null +++ b/samples/multiple-codebases/app-two.js @@ -0,0 +1,9 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export default { + fetch(request) { + return new Response(`App two: ${request.url}\n`); + }, +}; diff --git a/samples/multiple-codebases/config.capnp b/samples/multiple-codebases/config.capnp new file mode 100644 index 00000000000..fdbf8b0eb4a --- /dev/null +++ b/samples/multiple-codebases/config.capnp @@ -0,0 +1,25 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const multipleCodebasesExample :Workerd.Config = ( + services = [ + (name = "router", worker = ( + modules = [(name = "router", esModule = embed "router.js")], + compatibilityDate = "2023-02-28", + bindings = [ + (name = "APP_ONE", service = "app-one"), + (name = "APP_TWO", service = "app-two"), + ], + )), + (name = "app-one", worker = ( + modules = [(name = "app", esModule = embed "app-one.js")], + compatibilityDate = "2023-02-28", + )), + (name = "app-two", worker = ( + modules = [(name = "app", esModule = embed "app-two.js")], + compatibilityDate = "2023-02-28", + )), + ], + sockets = [ + (name = "http", address = "127.0.0.1:8080", http = (), service = "router"), + ], +); diff --git a/samples/multiple-codebases/router.js b/samples/multiple-codebases/router.js new file mode 100644 index 00000000000..576bee1ce3e --- /dev/null +++ b/samples/multiple-codebases/router.js @@ -0,0 +1,16 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +export default { + fetch(request, env) { + switch (new URL(request.url).hostname) { + case 'one.example.test': + return env.APP_ONE.fetch(request); + case 'two.example.test': + return env.APP_TWO.fetch(request); + default: + return new Response('Unknown host\n', { status: 404 }); + } + }, +};