heyajulia / energieprijzen

A bot that posts tomorrow's ANWB Energie prices to a Telegram channel every day.
https://t.me/s/energieprijzen
MIT License
0 stars 0 forks source link

WebAssembly research #22

Closed heyajulia closed 1 month ago

heyajulia commented 11 months ago

I was researching WebAssembly to hopefully be able to run the bot on Cloudflare Workers. However, sadly I never even got to the point where I could run it anywhere (and I'm not blaming anyone but myself). This document serves as a log of my journey.

Installing wasmtime and building the bot for WebAssembly is a piece of cake...

curl https://wasmtime.dev/install.sh -sSf | bash
GOOS=wasip1 GOARCH=wasm go build -o main.wasm cmd/bot/main.go

...but running it is a different story

wasmtime --env ENERGIEPRIJZEN_BOT_TOKEN=somevalue --env ENERGIEPRIJZEN_BOT_CHAT_ID=somevalue main.wasm
2023/11/11 14:35:58 main.go:52: Get "https://api.energyzero.nl/v1/energyprices?fromDate=2023-11-11T22%3A00%3A00Z&inclBtw=true&interval=4&tillDate=2023-11-12T21%3A59%3A59.999Z&usageType=1": dial tcp: lookup api.energyzero.nl on [::1]:53: dial udp [::1]:53: Connection refused

Side-stepping the DNS issue

Hard-coding the/an IP address also doesn't work:

Minimal reproducible example ```go package main import ( "fmt" "io" "net/http" ) func main() { const ip = `93.184.216.34` // example.com req, err := http.NewRequest("GET", "http://"+ip, nil) if err != nil { panic(err) } req.Host = "example.com" resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() b, err := io.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(b)) } ```
panic: Get "http://93.184.216.34": dial tcp 93.184.216.34:80: Connection refused

goroutine 1 [running]:
main.main()
        /workspaces/energieprijzen/main.go:20 +0x2e

stealthrocket/net to the rescue?

There's stealthrocket/net, which is mentioned in a post on the Go blog, but it doesn't seem to work for my use case:

import (
    // everything is the same as in the minimal reproducible example, except I've added this import:
    _ "github.com/stealthrocket/net/http"
)
Error: failed to run main module `main.wasm`

Caused by:
    0: failed to instantiate "main.wasm"
    1: unknown import: `wasi_snapshot_preview1::sock_open` has not been defined

The future is looking bright

My WebAssembly adventure ends here for now, but the future is looking bright. I found the WASI HTTP proposal, which is currently in phase 2 of 5. Fingers crossed!

heyajulia commented 11 months ago

Maybe... I could compile the bot to JS and run it that way? This modified MRE works completely fine in the GopherJS playground:

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    // 🚨 SECURITY: This is just to make it work in the GopherJS playground. Don't do this in prod.
    req, err := http.NewRequest("GET", "https://corsproxy.io?https://example.com", nil)
    if err != nil {
        panic(err)
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    b, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(b))
}

This is truly one of those "just because you could, doesn't mean you should" moments, but it does work:

(Note: main.go refers to the modified MRE)

go install golang.org/dl/go1.18.10@latest
../go/bin/go1.18.10 download
export GOPHERJS_GOROOT="$(../go/bin/go1.18.10 env GOROOT)"
go install github.com/gopherjs/gopherjs@v1.18.0-beta3
../go/bin/gopherjs build main.go
npm init -y
npm i xhr2
# ...manually patch main.js to add "global.XMLHttpRequest = require("xhr2");" near the top...
node main.js