diff --git a/cloudflare/env.go b/cloudflare/env.go index 85a28dd..82e8f48 100644 --- a/cloudflare/env.go +++ b/cloudflare/env.go @@ -2,6 +2,7 @@ package cloudflare import ( "context" + "syscall/js" "github.com/syumai/workers/cloudflare/internal/cfruntimecontext" ) @@ -12,3 +13,10 @@ import ( func Getenv(ctx context.Context, name string) string { return cfruntimecontext.GetRuntimeContextEnv(ctx).Get(name).String() } + +// GetBinding gets a value of an environment binding. +// - https://developers.cloudflare.com/workers/platform/bindings/about-service-bindings/ +// - This function panics when a runtime context is not found. +func GetBinding(ctx context.Context, name string) js.Value { + return cfruntimecontext.GetRuntimeContextEnv(ctx).Get(name) +} diff --git a/cloudflare/fetch/client.go b/cloudflare/fetch/client.go index 4b9c573..4a36d19 100644 --- a/cloudflare/fetch/client.go +++ b/cloudflare/fetch/client.go @@ -12,9 +12,30 @@ type Client struct { namespace js.Value } +// applyOptions applies client options. +func (c *Client) applyOptions(opts []ClientOption) { + for _, opt := range opts { + opt(c) + } +} + +// ClientOption is a type that represents an optional function. +type ClientOption func(*Client) + +// WithBinding changes the objects that Fetch API belongs to. +// This is useful for service bindings, mTLS, etc. +func WithBinding(bind js.Value) ClientOption { + return func(c *Client) { + c.namespace = bind + } +} + // NewClient returns new Client -func NewClient() *Client { - return &Client{ +func NewClient(opts ...ClientOption) *Client { + c := &Client{ namespace: jsutil.Global, } + c.applyOptions(opts) + + return c } diff --git a/examples/service-bindings/.gitignore b/examples/service-bindings/.gitignore new file mode 100644 index 0000000..53c37a1 --- /dev/null +++ b/examples/service-bindings/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/examples/service-bindings/Makefile b/examples/service-bindings/Makefile new file mode 100644 index 0000000..320ddc3 --- /dev/null +++ b/examples/service-bindings/Makefile @@ -0,0 +1,12 @@ +.PHONY: dev +dev: + wrangler dev + +.PHONY: build +build: + mkdir -p dist + tinygo build -o ./dist/app.wasm -target wasm ./... + +.PHONY: publish +publish: + wrangler publish diff --git a/examples/service-bindings/README.md b/examples/service-bindings/README.md new file mode 100644 index 0000000..07b6eff --- /dev/null +++ b/examples/service-bindings/README.md @@ -0,0 +1,32 @@ +# service-bindings + +- Service bindings are an API that facilitate Worker-to-Worker communication via explicit bindings defined in your configuration. +- In this example, invoke [hello](https://github.com/syumai/workers/tree/main/examples/hello) using Service bindings. + +## Development + +### Requirements + +This project requires these tools to be installed globally. + +* wrangler +* tinygo + +### Deploy Steps + +1. Deploy [hello](https://github.com/syumai/workers/tree/main/examples/hello) first. +2. Define service bindings in `wrangler.toml`. + ```toml + services = [ + { binding = "hello", service = "hello" } + ] + ``` +3. Deploy this example. + ``` + make build # build Go Wasm binary + make publish # publish worker + ``` + +## Documents + +- https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ \ No newline at end of file diff --git a/examples/service-bindings/go.mod b/examples/service-bindings/go.mod new file mode 100644 index 0000000..eb5269f --- /dev/null +++ b/examples/service-bindings/go.mod @@ -0,0 +1,7 @@ +module github.com/syumai/fetch + +go 1.18 + +require github.com/syumai/workers v0.0.0 + +replace github.com/syumai/workers => ../../ \ No newline at end of file diff --git a/examples/service-bindings/go.sum b/examples/service-bindings/go.sum new file mode 100644 index 0000000..8c27871 --- /dev/null +++ b/examples/service-bindings/go.sum @@ -0,0 +1,2 @@ +github.com/syumai/workers v0.1.0 h1:z5QfQR2X+PCKzom7RodpI5J4D5YF7NT7Qwzb9AM9dgY= +github.com/syumai/workers v0.1.0/go.mod h1:alXIDhTyeTwSzh0ZgQ3cb9HQPyyYfIejupE4Z3efr14= diff --git a/examples/service-bindings/main.go b/examples/service-bindings/main.go new file mode 100644 index 0000000..452c715 --- /dev/null +++ b/examples/service-bindings/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "io" + "net/http" + + "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare" + "github.com/syumai/workers/cloudflare/fetch" +) + +func main() { + handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + + obj := cloudflare.GetBinding(ctx, "hello") + cli := fetch.NewClient(fetch.WithBinding(obj)) + r, err := fetch.NewRequest(ctx, http.MethodGet, req.URL.String(), nil) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + res, err := cli.Do(r) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + io.Copy(w, res.Body) + }) + workers.Serve(handler) +} diff --git a/examples/service-bindings/worker.mjs b/examples/service-bindings/worker.mjs new file mode 100644 index 0000000..649ccf0 --- /dev/null +++ b/examples/service-bindings/worker.mjs @@ -0,0 +1,22 @@ +import "../assets/polyfill_performance.js"; +import "../assets/wasm_exec.js"; +import mod from "./dist/app.wasm"; + +const go = new Go(); + +const readyPromise = new Promise((resolve) => { + globalThis.ready = resolve; +}); + +const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { + go.run(instance); + return instance; +}); + +export default { + async fetch(req, env, ctx) { + await load; + await readyPromise; + return handleRequest(req, { env, ctx }); + } +} diff --git a/examples/service-bindings/wrangler.toml b/examples/service-bindings/wrangler.toml new file mode 100644 index 0000000..a053cca --- /dev/null +++ b/examples/service-bindings/wrangler.toml @@ -0,0 +1,9 @@ +name = "service-bindings" +main = "./worker.mjs" +compatibility_date = "2023-02-24" +services = [ + { binding = "hello", service = "hello" } +] + +[build] +command = "make build"