Merge pull request #19 from syumai/add-runtime-context

add runtime context
This commit is contained in:
syumai 2023-02-11 12:42:46 +09:00 committed by GitHub
commit 4b4a950109
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 204 additions and 179 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

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

View File

@ -10,7 +10,7 @@ import (
func main() { func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "MY_ENV: %s", cloudflare.Getenv("MY_ENV")) fmt.Fprintf(w, "MY_ENV: %s", cloudflare.Getenv(req.Context(), "MY_ENV"))
}) })
workers.Serve(handler) workers.Serve(handler)
} }

View File

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

View File

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

View File

@ -7,9 +7,8 @@ import (
"os" "os"
"strconv" "strconv"
"github.com/syumai/workers/cloudflare"
"github.com/syumai/workers" "github.com/syumai/workers"
"github.com/syumai/workers/cloudflare"
) )
// counterNamespace is a bounded KV namespace for storing counter. // counterNamespace is a bounded KV namespace for storing counter.
@ -25,35 +24,25 @@ func handleErr(w http.ResponseWriter, msg string, err error) {
} }
func main() { func main() {
// initialize KV namespace instance
kv, err := cloudflare.NewKVNamespace(counterNamespace)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to init KV: %v", err)
os.Exit(1)
}
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" { if req.URL.Path != "/" {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
return return
} }
// initialize KV namespace instance
kv, err := cloudflare.NewKVNamespace(req.Context(), counterNamespace)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to init KV: %v", err)
os.Exit(1)
}
countStr, err := kv.GetString(countKey, nil) countStr, err := kv.GetString(countKey, nil)
if err != nil { if err != nil {
handleErr(w, "failed to get current count\n", err) handleErr(w, "failed to get current count\n", err)
return return
} }
/*
countReader, err := kv.GetReader(countKey, nil)
if err != nil {
handleErr(w, "failed to get current count\n", err)
return
}
b, _ := io.ReadAll(countReader)
countStr := string(b)
*/
// ignore err and treat count value as 0 // ignore err and treat count value as 0
count, _ := strconv.Atoi(countStr) count, _ := strconv.Atoi(countStr)
@ -65,28 +54,7 @@ func main() {
return return
} }
/*
err = kv.PutReader(countKey, strings.NewReader(nextCountStr), nil)
if err != nil {
handleErr(w, "failed to put next count\n", err)
return
}
*/
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
/*
// List returns only `count` as the keys in this namespace.
v, err := kv.List(nil)
if err != nil {
handleErr(w, "failed to list\n", err)
return
}
for i, key := range v.Keys {
fmt.Fprintf(w, "%d: %s\n", i, key.Name)
}
*/
w.Write([]byte(nextCountStr)) w.Write([]byte(nextCountStr))
}) })

View File

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

View File

@ -1,11 +1,11 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
"os"
"strings" "strings"
"github.com/syumai/workers" "github.com/syumai/workers"
@ -22,20 +22,19 @@ func handleErr(w http.ResponseWriter, msg string, err error) {
w.Write([]byte(msg)) w.Write([]byte(msg))
} }
type server struct { type server struct{}
bucket *cloudflare.R2Bucket
}
func newServer() (*server, error) { func (s *server) bucket(ctx context.Context) (*cloudflare.R2Bucket, error) {
bucket, err := cloudflare.NewR2Bucket(bucketName) return cloudflare.NewR2Bucket(ctx, bucketName)
if err != nil {
return nil, err
}
return &server{bucket: bucket}, nil
} }
func (s *server) post(w http.ResponseWriter, req *http.Request, key string) { func (s *server) post(w http.ResponseWriter, req *http.Request, key string) {
objects, err := s.bucket.List() bucket, err := s.bucket(req.Context())
if err != nil {
handleErr(w, "failed to initialize R2Bucket\n", err)
return
}
objects, err := bucket.List()
if err != nil { if err != nil {
handleErr(w, "failed to list R2Objects\n", err) handleErr(w, "failed to list R2Objects\n", err)
return return
@ -47,7 +46,7 @@ func (s *server) post(w http.ResponseWriter, req *http.Request, key string) {
return return
} }
} }
_, err = s.bucket.Put(key, req.Body, &cloudflare.R2PutOptions{ _, err = bucket.Put(key, req.Body, &cloudflare.R2PutOptions{
HTTPMetadata: cloudflare.R2HTTPMetadata{ HTTPMetadata: cloudflare.R2HTTPMetadata{
ContentType: req.Header.Get("Content-Type"), ContentType: req.Header.Get("Content-Type"),
}, },
@ -64,7 +63,12 @@ func (s *server) post(w http.ResponseWriter, req *http.Request, key string) {
func (s *server) get(w http.ResponseWriter, req *http.Request, key string) { func (s *server) get(w http.ResponseWriter, req *http.Request, key string) {
// get image object from R2 // get image object from R2
imgObj, err := s.bucket.Get(key) bucket, err := s.bucket(req.Context())
if err != nil {
handleErr(w, "failed to initialize R2Bucket\n", err)
return
}
imgObj, err := bucket.Get(key)
if err != nil { if err != nil {
handleErr(w, "failed to get R2Object\n", err) handleErr(w, "failed to get R2Object\n", err)
return return
@ -86,7 +90,12 @@ func (s *server) get(w http.ResponseWriter, req *http.Request, key string) {
func (s *server) delete(w http.ResponseWriter, req *http.Request, key string) { func (s *server) delete(w http.ResponseWriter, req *http.Request, key string) {
// delete image object from R2 // delete image object from R2
if err := s.bucket.Delete(key); err != nil { bucket, err := s.bucket(req.Context())
if err != nil {
handleErr(w, "failed to initialize R2Bucket\n", err)
return
}
if err := bucket.Delete(key); err != nil {
handleErr(w, "failed to delete R2Object\n", err) handleErr(w, "failed to delete R2Object\n", err)
return return
} }
@ -114,10 +123,5 @@ func (s *server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
func main() { func main() {
s, err := newServer() workers.Serve(&server{})
if err != nil {
fmt.Fprintf(os.Stderr, "failed to start server: %v", err)
os.Exit(1)
}
workers.Serve(s)
} }

View File

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

View File

@ -23,7 +23,7 @@ func handleErr(w http.ResponseWriter, msg string, err error) {
// This example is based on implementation in syumai/workers-playground // This example is based on implementation in syumai/workers-playground
// - https://github.com/syumai/workers-playground/blob/e32881648ccc055e3690a0d9c750a834261c333e/r2-image-viewer/src/index.ts#L30 // - https://github.com/syumai/workers-playground/blob/e32881648ccc055e3690a0d9c750a834261c333e/r2-image-viewer/src/index.ts#L30
func handler(w http.ResponseWriter, req *http.Request) { func handler(w http.ResponseWriter, req *http.Request) {
bucket, err := cloudflare.NewR2Bucket(bucketName) bucket, err := cloudflare.NewR2Bucket(req.Context(), bucketName)
if err != nil { if err != nil {
handleErr(w, "failed to get R2Bucket\n", err) handleErr(w, "failed to get R2Bucket\n", err)
return return

View File

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

View File

@ -4,7 +4,6 @@ package app
import ( import (
json "encoding/json" json "encoding/json"
easyjson "github.com/mailru/easyjson" easyjson "github.com/mailru/easyjson"
jlexer "github.com/mailru/easyjson/jlexer" jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
@ -18,7 +17,7 @@ var (
_ easyjson.Marshaler _ easyjson.Marshaler
) )
func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(in *jlexer.Lexer, out *HelloResponse) { func easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(in *jlexer.Lexer, out *HelloResponse) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -49,7 +48,7 @@ func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(in *jlexer.Le
in.Consumed() in.Consumed()
} }
} }
func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(out *jwriter.Writer, in HelloResponse) { func easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(out *jwriter.Writer, in HelloResponse) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -64,27 +63,27 @@ func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(out *jwriter.
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v HelloResponse) MarshalJSON() ([]byte, error) { func (v HelloResponse) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(&w, v) easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v HelloResponse) MarshalEasyJSON(w *jwriter.Writer) { func (v HelloResponse) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp(w, v) easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *HelloResponse) UnmarshalJSON(data []byte) error { func (v *HelloResponse) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(&r, v) easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *HelloResponse) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *HelloResponse) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp(l, v) easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp(l, v)
} }
func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(in *jlexer.Lexer, out *HelloRequest) { func easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(in *jlexer.Lexer, out *HelloRequest) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -115,7 +114,7 @@ func easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(in *jlexer.L
in.Consumed() in.Consumed()
} }
} }
func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(out *jwriter.Writer, in HelloRequest) { func easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(out *jwriter.Writer, in HelloRequest) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -130,23 +129,23 @@ func easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(out *jwriter
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v HelloRequest) MarshalJSON() ([]byte, error) { func (v HelloRequest) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(&w, v) easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v HelloRequest) MarshalEasyJSON(w *jwriter.Writer) { func (v HelloRequest) MarshalEasyJSON(w *jwriter.Writer) {
easyjsonD2c14bEncodeGithubComSyumaiWorkersPlaygroundTinygoApp1(w, v) easyjsonD2c14bEncodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *HelloRequest) UnmarshalJSON(data []byte) error { func (v *HelloRequest) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(&r, v) easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *HelloRequest) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *HelloRequest) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjsonD2c14bDecodeGithubComSyumaiWorkersPlaygroundTinygoApp1(l, v) easyjsonD2c14bDecodeGithubComSyumaiWorkersExamplesSimpleJsonServerApp1(l, v)
} }

View File

@ -4,22 +4,19 @@ import mod from "./dist/app.wasm";
const go = new Go(); const go = new Go();
const readyPromise = new Promise((resolve) => {
globalThis.ready = resolve;
});
const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => { const load = WebAssembly.instantiate(mod, go.importObject).then((instance) => {
go.run(instance); go.run(instance);
return instance; return instance;
}); });
const readyPromise = new Promise((resolve) => { export default {
globalThis.ready = resolve; async fetch(req, env, ctx) {
}); await load;
await readyPromise;
async function processRequest(event) { return handleRequest(req, { env, ctx });
const req = event.request; }
await load;
await readyPromise;
return handleRequest(req);
} }
addEventListener("fetch", (event) => {
event.respondWith(processRequest(event));
})

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
}