TinyGo SDK
0.4 NEW — пишем балансер на Go, компилируем в WASM через TinyGo.
Установка TinyGo
brew tap tinygo-org/tools
brew install tinygo
tinygo version # 0.41+ нужен для wasi-таргета с reactor modeМинимальный плагин (30 строк)
wasm_modules/hello/main.go:
package main
import (
"encoding/json"
"lampac.cc/sdk/lampac"
)
func main() {} // обязателен для TinyGo wasi target
type item struct{ Name string `json:"name"` }
type movie struct {
Type string `json:"type"`
Data []item `json:"data"`
}
//export handle
func handle(ptr, length uint32) uint64 {
inv := lampac.ReadInvocation(ptr, length)
lampac.Info("got request, path=" + inv.Path)
body, _ := json.Marshal(movie{
Type: "movie",
Data: []item{{Name: "Hello, " + inv.Query["q"]}},
})
return lampac.WriteResponse(body)
}wasm_modules/hello/manifest.json:
{
"id": "hello",
"name": "Hello plugin",
"version": "0.1.0",
"target": "server",
"language": "tinygo",
"abi_version": 1,
"permissions": []
}wasm_modules/hello/go.mod:
module lampac.cc/wasm-modules/hello
go 1.22
require lampac.cc/sdk/lampac v0.0.0
replace lampac.cc/sdk/lampac => ../../wasm_sdk/tinygowasm_modules/hello/Makefile:
.PHONY: build
build:
tinygo build -o plugin.wasm -target wasi -no-debug ./
Сборка и тест:
make
curl 'http://localhost:9118/lite/hello?q=world'
# {"type":"movie","data":[{"name":"Hello, world"}]}Никакого рестарта lampac-go — fsnotify подхватил plugin.wasm автоматически.
Что есть в SDK
wasm_sdk/tinygo/lampac.go экспортирует:
Лог
lampac.Debug("…")
lampac.Info("…")
lampac.Warn("…")
lampac.Error("…")HTTP
resp, err := lampac.HTTPGet(lampac.HTTPRequest{
URL: "https://api.example.com/v1/movies?id=123",
Headers: map[string]string{"User-Agent": "lampac"},
})
// resp.Status, resp.Headers, resp.Body, resp.Err
resp, err := lampac.HTTPPost(lampac.HTTPRequest{
URL: "https://api.example.com/auth",
Body: `{"login":"x","pass":"y"}`,
Headers: map[string]string{"Content-Type": "application/json"},
})resp.Body — уже декодированные байты (хост возвращает base64, SDK раскодирует).
Proxy URL
url := lampac.ProxyURL("https://cdn.example.com/movie.m3u8", lampac.ProxyOpts{
Plugin: "hello", // опционально
Headers: map[string]string{"Origin": "..."}, // headers для upstream
})
// url == "https://lampac-host/proxy/<aes-blob>"Кеш
lampac.CacheSet("user:42", []byte(`{"name":"alice"}`), 60) // ttl 60 сек
val, ok := lampac.CacheGet("user:42") // []byte, boolInvocation
inv := lampac.ReadInvocation(ptr, length)
// inv.Query, inv.Headers, inv.Host, inv.RequestIP,
// inv.Path, inv.Life, inv.Checksearch, inv.UserAgent, inv.Config
var cfg struct {
APIHost string `json:"apiHost"`
Token string `json:"token"`
}
_ = json.Unmarshal(inv.Config, &cfg)Ответ
return lampac.WriteResponse(jsonBytes) // плагин владеет lifecycle памятиПолный пример с HTTP + кешом
package main
import (
"encoding/json"
"fmt"
"lampac.cc/sdk/lampac"
)
func main() {}
type cfg struct {
APIHost string `json:"apiHost"`
}
//export handle
func handle(ptr, length uint32) uint64 {
inv := lampac.ReadInvocation(ptr, length)
var c cfg
_ = json.Unmarshal(inv.Config, &c)
id := inv.Query["id"]
if id == "" {
return lampac.WriteResponse([]byte(`{"data":[]}`))
}
cacheKey := "movie:" + id
if v, ok := lampac.CacheGet(cacheKey); ok {
return lampac.WriteResponse(v) // hit — отдаём без HTTP
}
resp, err := lampac.HTTPGet(lampac.HTTPRequest{
URL: fmt.Sprintf("%s/api/movies/%s", c.APIHost, id),
})
if err != nil || resp.Status != 200 {
return lampac.WriteResponse([]byte(`{"data":[],"rch":false}`))
}
lampac.CacheSet(cacheKey, resp.Body, 600)
return lampac.WriteResponse(resp.Body)
}Размер бинарника
| Что собрано | TinyGo (-no-debug) |
|---|---|
| Echo (демо со всеми host-импортами) | ~410 КБ |
| Quality filter (middleware) | ~420 КБ |
| Welcome toast (только client API, без net/json) | ~24 КБ |
tinygo build -no-debug -opt=z (по умолчанию -opt=2) сократит ещё на 10–15%.
Подводные камни
- Не используй
os.Exit/panic— модуль закроется, до следующего запроса всё равно создастся новый, но крэш-дамп фиксируется. - JSON через
encoding/jsonтянет ~200 КБ — если плагин маленький и тебе хватит ручной сборки строк, лучше так. - Гонки: одна wazero-инстанция на модуль обслуживает все запросы под
sync.Mutex. Если плагин CPU-тяжёлый, пишите так, чтобыhandle()возвращался быстро. unsafe.Pointer(&b[0])на пустом срезе паникует — SDK уже это учитывает, но если вы делаете свои imports вручную, не забудьте.
Что ещё посмотреть
wasm_modules/echo— все host-импорты разомwasm_modules/quality_filter— пример middlewarewasm_modules/welcome_toast— client target