From 9e87698fac9b9118e80e167d2cdde57263ecc40f Mon Sep 17 00:00:00 2001 From: syumai Date: Sat, 11 Feb 2023 10:30:51 +0900 Subject: [PATCH] add runtime context --- cloudflare/env.go | 10 +++++---- cloudflare/kv.go | 8 ++++--- cloudflare/r2bucket.go | 8 ++++--- cloudflare/runtimecontext.go | 35 ++++++++++++++++++++++++++++++ handler.go | 15 ++++++++++--- internal/runtimecontext/context.go | 25 +++++++++++++++++++++ 6 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 cloudflare/runtimecontext.go create mode 100644 internal/runtimecontext/context.go diff --git a/cloudflare/env.go b/cloudflare/env.go index 3d74986..75c09e6 100644 --- a/cloudflare/env.go +++ b/cloudflare/env.go @@ -1,10 +1,12 @@ package cloudflare -import "github.com/syumai/workers/internal/jsutil" +import ( + "context" +) // Getenv gets a value of an environment variable. // - https://developers.cloudflare.com/workers/platform/environment-variables/ -// - Technically, this function is just an alias for js.Global().Get(env_name).String(). -func Getenv(name string) string { - return jsutil.Global.Get(name).String() +// - This function panics when a runtime context is not found. +func Getenv(ctx context.Context, name string) string { + return getRuntimeContextEnv(ctx).Get(name).String() } diff --git a/cloudflare/kv.go b/cloudflare/kv.go index f5d0387..13da052 100644 --- a/cloudflare/kv.go +++ b/cloudflare/kv.go @@ -1,6 +1,7 @@ package cloudflare import ( + "context" "fmt" "io" "syscall/js" @@ -17,9 +18,10 @@ type KVNamespace struct { // NewKVNamespace returns KVNamespace for given variable name. // - variable name must be defined in wrangler.toml as kv_namespace's binding. -// - if the given variable name doesn't exist on Global object, returns error. -func NewKVNamespace(varName string) (*KVNamespace, error) { - inst := js.Global().Get(varName) +// - if the given variable name doesn't exist on runtime context, returns error. +// - This function panics when a runtime context is not found. +func NewKVNamespace(ctx context.Context, varName string) (*KVNamespace, error) { + inst := getRuntimeContextEnv(ctx).Get(varName) if inst.IsUndefined() { return nil, fmt.Errorf("%s is undefined", varName) } diff --git a/cloudflare/r2bucket.go b/cloudflare/r2bucket.go index 2fbc899..3697d04 100644 --- a/cloudflare/r2bucket.go +++ b/cloudflare/r2bucket.go @@ -1,6 +1,7 @@ package cloudflare import ( + "context" "fmt" "io" "syscall/js" @@ -18,9 +19,10 @@ type R2Bucket struct { // NewR2Bucket returns R2Bucket for given variable name. // - variable name must be defined in wrangler.toml. // - see example: https://github.com/syumai/workers/tree/main/examples/r2-image-viewer -// - if the given variable name doesn't exist on Global object, returns error. -func NewR2Bucket(varName string) (*R2Bucket, error) { - inst := js.Global().Get(varName) +// - if the given variable name doesn't exist on runtime context, returns error. +// - This function panics when a runtime context is not found. +func NewR2Bucket(ctx context.Context, varName string) (*R2Bucket, error) { + inst := getRuntimeContextEnv(ctx).Get(varName) if inst.IsUndefined() { return nil, fmt.Errorf("%s is undefined", varName) } diff --git a/cloudflare/runtimecontext.go b/cloudflare/runtimecontext.go new file mode 100644 index 0000000..bc3fd2d --- /dev/null +++ b/cloudflare/runtimecontext.go @@ -0,0 +1,35 @@ +package cloudflare + +import ( + "context" + "syscall/js" + + "github.com/syumai/workers/internal/runtimecontext" +) + +/** + * The type definition of RuntimeContext for Cloudflare Worker expects: + * ```ts + * type RuntimeContext { + * env: Env; + * ctx: ExecutionContext; + * } + * ``` + * This type is based on the type definition of ExportedHandlerFetchHandler. + * - see: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#LL564 + */ + +// getRuntimeContextEnv gets object which holds environment variables bound to Cloudflare worker. +// - see: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#L566 +func getRuntimeContextEnv(ctx context.Context) js.Value { + runtimeCtxValue := runtimecontext.MustExtract(ctx) + return runtimeCtxValue.Get("env") +} + +// getExecutionContext gets ExecutionContext object from context. +// - see: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#L567 +// - see also: https://github.com/cloudflare/workers-types/blob/c8d9533caa4415c2156d2cf1daca75289d01ae70/index.d.ts#L554 +func getExecutionContext(ctx context.Context) js.Value { + runtimeCtxValue := runtimecontext.MustExtract(ctx) + return runtimeCtxValue.Get("ctx") +} diff --git a/handler.go b/handler.go index 4612961..bc9232a 100644 --- a/handler.go +++ b/handler.go @@ -1,12 +1,14 @@ package workers import ( + "context" "fmt" "io" "net/http" "syscall/js" "github.com/syumai/workers/internal/jsutil" + "github.com/syumai/workers/internal/runtimecontext" ) var httpHandler http.Handler @@ -14,15 +16,20 @@ var httpHandler http.Handler func init() { var handleRequestCallback js.Func handleRequestCallback = js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 1 { + if len(args) > 2 { panic(fmt.Errorf("too many args given to handleRequest: %d", len(args))) } + reqObj := args[0] + runtimeCtxObj := js.Null() + if len(args) > 1 { + runtimeCtxObj = args[1] + } var cb js.Func cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any { defer cb.Release() resolve := pArgs[0] go func() { - res, err := handleRequest(args[0]) + res, err := handleRequest(reqObj, runtimeCtxObj) if err != nil { panic(err) } @@ -36,7 +43,7 @@ func init() { } // handleRequest accepts a Request object and returns Response object. -func handleRequest(reqObj js.Value) (js.Value, error) { +func handleRequest(reqObj js.Value, runtimeCtxObj js.Value) (js.Value, error) { if httpHandler == nil { return js.Value{}, fmt.Errorf("Serve must be called before handleRequest.") } @@ -44,6 +51,8 @@ func handleRequest(reqObj js.Value) (js.Value, error) { if err != nil { panic(err) } + ctx := runtimecontext.New(context.Background(), runtimeCtxObj) + req = req.WithContext(ctx) reader, writer := io.Pipe() w := &responseWriterBuffer{ header: http.Header{}, diff --git a/internal/runtimecontext/context.go b/internal/runtimecontext/context.go new file mode 100644 index 0000000..aed2463 --- /dev/null +++ b/internal/runtimecontext/context.go @@ -0,0 +1,25 @@ +package runtimecontext + +import ( + "context" + "errors" + "syscall/js" +) + +type runtimeCtxKey struct{} + +func New(ctx context.Context, runtimeCtxObj js.Value) context.Context { + return context.WithValue(ctx, runtimeCtxKey{}, runtimeCtxObj) +} + +var ErrRuntimeContextNotFound = errors.New("runtime context was not found") + +// MustExtract extracts runtime context object from context. +// This function panics when runtime context object was not found. +func MustExtract(ctx context.Context) js.Value { + v, ok := ctx.Value(runtimeCtxKey{}).(js.Value) + if !ok { + panic(ErrRuntimeContextNotFound) + } + return v +}