├── .gitignore ├── Makefile ├── README.md ├── go.go ├── go.mod ├── metadata.json ├── package-lock.json ├── package.json ├── polyfill.js └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .parcel-cache/ 3 | dist/ 4 | node_modules/ 5 | 6 | .env 7 | .envrc 8 | wasm_exec.js 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | build: export GOOS=js 4 | build: export GOARCH=wasm 5 | build: 6 | rm -rf .cache dist 7 | 8 | cp "${GOROOT}/misc/wasm/wasm_exec.js" . 9 | npm install 10 | npm run build 11 | 12 | go build -o dist/go.wasm go.go 13 | 14 | cp metadata.json dist/metadata.json 15 | sed -i 's/PRIVATE_KEY_PLACEHOLDER/${PRIVATE_KEY}/g' dist/metadata.json 16 | 17 | curl -X PUT \ 18 | "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/workers/scripts/${CF_WORKER_NAME}" \ 19 | -H "Authorization: Bearer ${CF_API_TOKEN}" \ 20 | -F "metadata=@dist/metadata.json;type=application/json" \ 21 | -F "script=@dist/worker.js;type=application/javascript" \ 22 | -F "wasm=@dist/go.wasm;type=application/wasm" > /dev/null # the response is too verbose 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cloudflare worker that calls into golang code to perform computational heavy processing 2 | i saw a 15x performance increase compared to the native js implementation of the same algo 3 | 4 | to run `make build` you will need the following env vars: 5 | 6 | ``` 7 | GOROOT= 8 | CF_WORKER_NAME= 9 | CF_ACCOUNT_ID= 10 | CF_API_TOKEN= 11 | PRIVATE_KEY= 12 | ``` 13 | -------------------------------------------------------------------------------- /go.go: -------------------------------------------------------------------------------- 1 | //+ build js,wasm 2 | 3 | package main 4 | 5 | import ( 6 | "crypto/ed25519" 7 | "encoding/base64" 8 | "strings" 9 | "syscall/js" 10 | ) 11 | 12 | var ( 13 | privateKey ed25519.PrivateKey 14 | publicKey ed25519.PublicKey 15 | ) 16 | 17 | func sign(_ js.Value, input []js.Value) interface{} { 18 | message := []byte(input[0].String()) 19 | callback := input[1] 20 | 21 | messageB64 := make([]byte, base64.RawURLEncoding.EncodedLen(len(message))) 22 | base64.RawURLEncoding.Encode(messageB64, message) 23 | 24 | signature := base64.RawURLEncoding.EncodeToString(ed25519.Sign(privateKey, messageB64)) 25 | token := strings.Join([]string{string(messageB64), signature}, ".") 26 | 27 | callback.Invoke(token) 28 | return nil 29 | } 30 | 31 | func verify(_ js.Value, input []js.Value) interface{} { 32 | token := strings.Split(input[0].String(), ".") 33 | callback := input[1] 34 | 35 | signature, err := base64.RawURLEncoding.DecodeString(token[1]) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | callback.Invoke(ed25519.Verify(publicKey, []byte(token[0]), signature)) 41 | return nil 42 | } 43 | 44 | func main() { 45 | var err error 46 | 47 | privateKey, err = base64.RawURLEncoding.DecodeString(js.Global().Get("PRIVATE_KEY").String()) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | publicKey = make([]byte, ed25519.PublicKeySize) 53 | copy(publicKey, privateKey[32:]) 54 | 55 | js.Global().Set("sign", js.FuncOf(sign)) 56 | js.Global().Set("verify", js.FuncOf(verify)) 57 | <-make(chan interface{}) 58 | } 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sepbot/cloudflare-worker-golang 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "body_part": "script", 3 | "bindings": [ 4 | { 5 | "type": "wasm_module", 6 | "name": "WASM", 7 | "part": "wasm" 8 | }, { 9 | "type": "secret_text", 10 | "name": "PRIVATE_KEY", 11 | "text": "PRIVATE_KEY_PLACEHOLDER" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudflare-worker-golang", 3 | "private": true, 4 | "scripts": { 5 | "build": "parcel build --no-source-maps worker.js" 6 | }, 7 | "devDependencies": { 8 | "parcel": "2.0.0-nightly.427" 9 | }, 10 | "browserslist": [ 11 | "last 1 Chrome version" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | performance = { 2 | now: Date.now, 3 | }; 4 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | require('./polyfill.js'); 2 | require('./wasm_exec.js'); 3 | 4 | addEventListener('fetch', (event) => { 5 | event.respondWith(handleRequest(event.request)); 6 | }); 7 | 8 | function handleRequest(req) { 9 | return new Promise((async (resolve, reject) => { 10 | try { 11 | const url = new URL(req.url); 12 | const go = new Go(); 13 | const instance = await WebAssembly.instantiate(WASM, go.importObject); 14 | go.run(instance); 15 | switch (url.pathname) { 16 | case '/sign': 17 | sign(url.searchParams.get('message'), (answer) => { 18 | resolve(new Response(answer, { status: 200 })); 19 | }); 20 | break; 21 | case '/verify': 22 | verify(url.searchParams.get('token'), (answer) => { 23 | resolve(new Response(answer, { status: 200 })); 24 | }); 25 | break; 26 | default: 27 | resolve(new Response('', { status: 404 })); 28 | break; 29 | } 30 | } catch (e) { 31 | console.log(e); 32 | reject(new Response(JSON.stringify(e), { status: 500 })); 33 | } 34 | })); 35 | } 36 | --------------------------------------------------------------------------------