add runtime context

This commit is contained in:
syumai 2023-02-11 10:30:51 +09:00
parent 0e499ad79c
commit 9e87698fac
6 changed files with 88 additions and 13 deletions

View File

@ -1,10 +1,12 @@
package cloudflare package cloudflare
import "github.com/syumai/workers/internal/jsutil" import (
"context"
)
// Getenv gets a value of an environment variable. // Getenv gets a value of an environment variable.
// - https://developers.cloudflare.com/workers/platform/environment-variables/ // - https://developers.cloudflare.com/workers/platform/environment-variables/
// - Technically, this function is just an alias for js.Global().Get(env_name).String(). // - This function panics when a runtime context is not found.
func Getenv(name string) string { func Getenv(ctx context.Context, name string) string {
return jsutil.Global.Get(name).String() return getRuntimeContextEnv(ctx).Get(name).String()
} }

View File

@ -1,6 +1,7 @@
package cloudflare package cloudflare
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"syscall/js" "syscall/js"
@ -17,9 +18,10 @@ type KVNamespace struct {
// NewKVNamespace returns KVNamespace for given variable name. // NewKVNamespace returns KVNamespace for given variable name.
// - variable name must be defined in wrangler.toml as kv_namespace's binding. // - 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. // - if the given variable name doesn't exist on runtime context, returns error.
func NewKVNamespace(varName string) (*KVNamespace, error) { // - This function panics when a runtime context is not found.
inst := js.Global().Get(varName) func NewKVNamespace(ctx context.Context, varName string) (*KVNamespace, error) {
inst := getRuntimeContextEnv(ctx).Get(varName)
if inst.IsUndefined() { if inst.IsUndefined() {
return nil, fmt.Errorf("%s is undefined", varName) return nil, fmt.Errorf("%s is undefined", varName)
} }

View File

@ -1,6 +1,7 @@
package cloudflare package cloudflare
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"syscall/js" "syscall/js"
@ -18,9 +19,10 @@ type R2Bucket struct {
// NewR2Bucket returns R2Bucket for given variable name. // NewR2Bucket returns R2Bucket for given variable name.
// - variable name must be defined in wrangler.toml. // - variable name must be defined in wrangler.toml.
// - see example: https://github.com/syumai/workers/tree/main/examples/r2-image-viewer // - 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. // - if the given variable name doesn't exist on runtime context, returns error.
func NewR2Bucket(varName string) (*R2Bucket, error) { // - This function panics when a runtime context is not found.
inst := js.Global().Get(varName) func NewR2Bucket(ctx context.Context, varName string) (*R2Bucket, error) {
inst := getRuntimeContextEnv(ctx).Get(varName)
if inst.IsUndefined() { if inst.IsUndefined() {
return nil, fmt.Errorf("%s is undefined", varName) return nil, fmt.Errorf("%s is undefined", varName)
} }

View File

@ -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")
}

View File

@ -1,12 +1,14 @@
package workers package workers
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"syscall/js" "syscall/js"
"github.com/syumai/workers/internal/jsutil" "github.com/syumai/workers/internal/jsutil"
"github.com/syumai/workers/internal/runtimecontext"
) )
var httpHandler http.Handler var httpHandler http.Handler
@ -14,15 +16,20 @@ var httpHandler http.Handler
func init() { func init() {
var handleRequestCallback js.Func var handleRequestCallback js.Func
handleRequestCallback = js.FuncOf(func(this js.Value, args []js.Value) any { 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))) 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 var cb js.Func
cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any { cb = js.FuncOf(func(_ js.Value, pArgs []js.Value) any {
defer cb.Release() defer cb.Release()
resolve := pArgs[0] resolve := pArgs[0]
go func() { go func() {
res, err := handleRequest(args[0]) res, err := handleRequest(reqObj, runtimeCtxObj)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -36,7 +43,7 @@ func init() {
} }
// handleRequest accepts a Request object and returns Response object. // 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 { if httpHandler == nil {
return js.Value{}, fmt.Errorf("Serve must be called before handleRequest.") 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 { if err != nil {
panic(err) panic(err)
} }
ctx := runtimecontext.New(context.Background(), runtimeCtxObj)
req = req.WithContext(ctx)
reader, writer := io.Pipe() reader, writer := io.Pipe()
w := &responseWriterBuffer{ w := &responseWriterBuffer{
header: http.Header{}, header: http.Header{},

View File

@ -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
}