diff --git a/README.md b/README.md index 37818c1..8c5e128 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,7 @@ - [x] Calling stubs * [x] D1 (alpha) * [x] Environment variables -* FetchEvent's [lifecycle methods](https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#lifecycle-methods) - - [x] waitUntil - - [ ] respondWith - - [ ] passThroughOnException +* [x] FetchEvent ## Installation diff --git a/cloudflare/fetchevent.go b/cloudflare/fetchevent.go index 9fa64ce..2c63db7 100644 --- a/cloudflare/fetchevent.go +++ b/cloudflare/fetchevent.go @@ -12,9 +12,8 @@ import ( // It accepts an asynchronous task which the Workers runtime will execute before the handler terminates but without blocking the response. // see: https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#waituntil func WaitUntil(ctx context.Context, task func()) { - executionContext := cfruntimecontext.GetExecutionContext(ctx) - - executionContext.Call("waitUntil", jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { + exCtx := cfruntimecontext.GetExecutionContext(ctx) + exCtx.Call("waitUntil", jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { resolve := pArgs[0] go func() { task() @@ -23,3 +22,18 @@ func WaitUntil(ctx context.Context, task func()) { return js.Undefined() }))) } + +// PassThroughOnException prevents a runtime error response when the Worker script throws an unhandled exception. +// Instead, the request forwards to the origin server as if it had not gone through the worker. +// see: https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#passthroughonexception +func PassThroughOnException(ctx context.Context) { + exCtx := cfruntimecontext.GetExecutionContext(ctx) + jsutil.AwaitPromise(jsutil.NewPromise(js.FuncOf(func(this js.Value, pArgs []js.Value) any { + resolve := pArgs[0] + go func() { + exCtx.Call("passThroughOnException") + resolve.Invoke(js.Undefined()) + }() + return js.Undefined() + }))) +} diff --git a/examples/wait-until/.gitignore b/examples/fetch-event/.gitignore similarity index 100% rename from examples/wait-until/.gitignore rename to examples/fetch-event/.gitignore diff --git a/examples/fetch-event/Makefile b/examples/fetch-event/Makefile new file mode 100644 index 0000000..11a0197 --- /dev/null +++ b/examples/fetch-event/Makefile @@ -0,0 +1,13 @@ +.PHONY: dev +dev: + wrangler dev + +.PHONY: build +build: + mkdir -p dist + #tinygo build -o ./dist/app.wasm -target wasm ./... + tinygo build -o ./dist/app.wasm -target wasm -no-debug ./... + +.PHONY: publish +publish: + wrangler publish diff --git a/examples/fetch-event/README.md b/examples/fetch-event/README.md new file mode 100644 index 0000000..40923eb --- /dev/null +++ b/examples/fetch-event/README.md @@ -0,0 +1,48 @@ +# [FetchEvent](https://developers.cloudflare.com/workers/runtime-apis/fetch-event/) + +Normally, workers are designed to return some kind of HTTP Response and exit immediately upon receiving an HTTP request. `FetchEvent` can extend these life cycles. + +#### WaitUntil + +`WaitUntil` extends the lifetime of the "fetch" event. It accepts an asynchronous task which the Workers runtime will execute without blocking the response. The worker will not be terminated until those tasks are completed. + +#### PassThroughOnException + +`PassThroughOnException` prevents a runtime error response when the Worker script throws an unhandled exception. Instead, the request forwards to the origin server as if it had not gone through the worker. + +## Example + +### Usecase + +You have decided to implement a log stream API to capture all access logs. You must edit the Headers so that the user's API token is not logged. If an unknown error occurs during this process, the entire service will be down, which must be avoided. +In such cases, declare PassThroughOnException first and use WaitUntil for logging. + +### Setup + +This example worker is triggered by [Routes](https://developers.cloudflare.com/workers/platform/triggers/routes/). To try this example, add your site to cloudflare and add some records(A and CNAME, etc.) so that you can actually access the website. +If your domain is `sub.example.com`, edit `wrangler.toml` as following: + +```toml +routes = [ + { pattern = "sub.example.com/*", zone_name = "example.com" } +] +``` + +The workers is executed if the URL matches `sub.example.com/*`. + +### Development + +#### Requirements + +This project requires these tools to be installed globally. + +* wrangler +* tinygo + +#### Commands + +``` +make dev # run dev server +make build # build Go Wasm binary +make publish # publish worker +``` \ No newline at end of file diff --git a/examples/wait-until/go.mod b/examples/fetch-event/go.mod similarity index 100% rename from examples/wait-until/go.mod rename to examples/fetch-event/go.mod diff --git a/examples/wait-until/go.sum b/examples/fetch-event/go.sum similarity index 100% rename from examples/wait-until/go.sum rename to examples/fetch-event/go.sum diff --git a/examples/fetch-event/main.go b/examples/fetch-event/main.go new file mode 100644 index 0000000..4e56a84 --- /dev/null +++ b/examples/fetch-event/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httputil" + "time" + + "github.com/syumai/workers" + "github.com/syumai/workers/cloudflare" + "github.com/syumai/workers/cloudflare/fetch" +) + +func handler(w http.ResponseWriter, req *http.Request) { + ctx := req.Context() + + cloudflare.PassThroughOnException(ctx) + + // logging after responding + cloudflare.WaitUntil(ctx, func() { + for i := 0; i < 5; i++ { + time.Sleep(time.Second) + } + fmt.Println("5-second task completed") + }) + + // panic if x-error header has provided + if req.Header.Get("x-error") != "" { + panic("error") + } + + // responds with origin server + fc := fetch.NewClient() + proxy := httputil.ReverseProxy{ + Transport: fc.HTTPClient().Transport, + Director: func(r *http.Request) { + r.URL = req.URL + }, + } + + proxy.ServeHTTP(w, req) +} + +func main() { + workers.Serve(http.HandlerFunc(handler)) +} diff --git a/examples/wait-until/worker.mjs b/examples/fetch-event/worker.mjs similarity index 100% rename from examples/wait-until/worker.mjs rename to examples/fetch-event/worker.mjs diff --git a/examples/fetch-event/wrangler.toml b/examples/fetch-event/wrangler.toml new file mode 100644 index 0000000..60e0df1 --- /dev/null +++ b/examples/fetch-event/wrangler.toml @@ -0,0 +1,10 @@ +name = "fetch-event" +main = "./worker.mjs" +compatibility_date = "2023-02-24" + +routes = [ + { pattern = "example.com/*", zone_name = "example.com" } +] + +[build] +command = "make build" diff --git a/examples/wait-until/Makefile b/examples/wait-until/Makefile deleted file mode 100644 index 320ddc3..0000000 --- a/examples/wait-until/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -.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/wait-until/README.md b/examples/wait-until/README.md deleted file mode 100644 index 8b27649..0000000 --- a/examples/wait-until/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# wait-until - -* Executes a task in worker even after the server has returned the response. -* This example executes 5-second task after responding and outputs the console log. - -## Document - -* https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#waituntil - -## Development - -### Requirements - -This project requires these tools to be installed globally. - -* wrangler -* tinygo - -### Commands - -``` -make dev # run dev server -make build # build Go Wasm binary -make publish # publish worker -``` diff --git a/examples/wait-until/main.go b/examples/wait-until/main.go deleted file mode 100644 index c93d51a..0000000 --- a/examples/wait-until/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "time" - - "github.com/syumai/workers" - "github.com/syumai/workers/cloudflare" -) - -func main() { - handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - cloudflare.WaitUntil(req.Context(), func() { - for i := 0; i < 5; i++ { - time.Sleep(time.Second) - } - - fmt.Println("5-second task completed") - }) - - w.Write([]byte("http response done")) - }) - workers.Serve(handler) -} diff --git a/examples/wait-until/wrangler.toml b/examples/wait-until/wrangler.toml deleted file mode 100644 index 7c941bc..0000000 --- a/examples/wait-until/wrangler.toml +++ /dev/null @@ -1,6 +0,0 @@ -name = "wait-until" -main = "./worker.mjs" -compatibility_date = "2023-02-24" - -[build] -command = "make build"