From e507ba0981774f09b1e4fb3287d731fca797f2eb Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 24 Oct 2026 00:26:13 +0200 Subject: [PATCH] examples/net: add httphi versions of webserver and webstatic Use the heapless httphi router from github.com/soypat/lneto instead of net/http. This uses about a third of the flash and half the static RAM, and lets webserver fit on the arduino-nano33. Bumps go.mod to go 1.24 as required by lneto. --- examples/net/webserver-httphi/main.go | 210 ++++++++++++++++++ .../webstatic-httphi/images/tinygo-logo.png | Bin 0 -> 30898 bytes examples/net/webstatic-httphi/index.html | 28 +++ examples/net/webstatic-httphi/main.go | 123 ++++++++++ go.mod | 5 +- go.sum | 2 + smoketest.go | 1 + smoketest.sh | 3 + 8 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 examples/net/webserver-httphi/main.go create mode 100644 examples/net/webstatic-httphi/images/tinygo-logo.png create mode 100644 examples/net/webstatic-httphi/index.html create mode 100644 examples/net/webstatic-httphi/main.go diff --git a/examples/net/webserver-httphi/main.go b/examples/net/webserver-httphi/main.go new file mode 100644 index 000000000..57b955d34 --- /dev/null +++ b/examples/net/webserver-httphi/main.go @@ -0,0 +1,210 @@ +// This example is the webserver example using the heapless httphi router +// from github.com/soypat/lneto instead of "net/http". +// +// $ curl http://10.0.0.2 + +//go:build ninafw || wioterminal + +package main + +import ( + "log" + "machine" + "net" + "strconv" + "time" + + "github.com/soypat/lneto/http/httphi" + "github.com/soypat/lneto/http/httpraw" + + "tinygo.org/x/drivers/netlink" + "tinygo.org/x/drivers/netlink/probe" +) + +var ( + ssid string + pass string + port string = ":80" +) + +const ( + numWorkers = 2 + memoryPerConn = 1024 +) + +var led = machine.LED + +func main() { + + // wait a bit for serial + time.Sleep(2 * time.Second) + + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + link, _ := probe.Probe() + + err := link.NetConnect(&netlink.ConnectParams{ + Ssid: ssid, + Passphrase: pass, + }) + if err != nil { + log.Fatal(err) + } + + var mux httphi.MuxSlice + mux.Handle("GET /{$}", root) + mux.Handle("GET /hello", hello) + mux.Handle("/cnt", cnt) + mux.Handle("GET /6", sixlines) + mux.Handle("GET /off", LED_OFF) + mux.Handle("GET /on", LED_ON) + + var router httphi.Router + cfg := httphi.DefaultRouterConfig(numWorkers, memoryPerConn, mux.MaxPathValues()) + err = router.Configure(&mux, cfg) + if err != nil { + log.Fatal(err) + } + + listener, err := net.Listen("tcp", port) + if err != nil { + log.Fatal(err) + } + + for { + conn, err := listener.Accept() + if err != nil { + println("accept:", err.Error()) + time.Sleep(time.Second) + continue + } + if err := router.Handle(conn); err != nil { + conn.Close() + } + } +} + +func root(ex *httphi.Exchange) { + access := int64(1) + + var cookie httpraw.Cookie + var cookieBuf [128]byte + cookie.Reset(cookieBuf[:0], 4) + if ex.RequestParseCookie(&cookie, "Cookie") == nil { + if v := cookie.Get("access"); v != nil { + n, err := strconv.ParseInt(string(v), 10, 0) + if err != nil { + ex.RespondString(httphi.StatusBadRequest, "text/plain", "invalid cookie value") + return + } + access = n + 1 + } + } + + var setCookie [32]byte + ex.StageHeaderBytes("Set-Cookie", strconv.AppendInt(append(setCookie[:0], "access="...), access, 10)) + ex.StageHeader("Content-Type", "text/html; charset=UTF-8") + ex.StageHeader("Connection", "close") + + var num [20]byte + ex.WriteBodyString(rootHead) + ex.WriteBody(strconv.AppendInt(num[:0], access, 10)) + ex.WriteBodyString(rootTail) +} + +const rootHead = ` + + + TinyGo HTTP Server + + + +
TinyGo HTTP Server
+ +

+ access: ` + +const rootTail = ` +

+ + /hello
+ /6
+ +

+ LED
+ /on
+ /off
+

+ + +

+ /cnt
+ cnt:
+ incrCnt()
+

+ + +
+

+ + +` + +func sixlines(ex *httphi.Exchange) { + // https://fukuno.jig.jp/3267 + ex.RespondString(httphi.StatusOK, "text/html; charset=UTF-8", ``) +} + +func LED_ON(ex *httphi.Exchange) { + led.High() + ex.RespondString(httphi.StatusOK, "text/plain; charset=UTF-8", "led.High()") +} + +func LED_OFF(ex *httphi.Exchange) { + led.Low() + ex.RespondString(httphi.StatusOK, "text/plain; charset=UTF-8", "led.Low()") +} + +func hello(ex *httphi.Exchange) { + ex.RespondString(httphi.StatusOK, "text/plain; charset=UTF-8", "hello") +} + +var counter int64 + +func cnt(ex *httphi.Exchange) { + if ex.RequestMethod() == httphi.MethPost { + var form httpraw.Form + var formBuf [128]byte + form.Reset(formBuf[:0], 4) + form.EnableBufferGrowth(false) + if err := ex.RequestParseForm(&form, true, false); err != nil { + ex.RespondString(httphi.StatusBadRequest, "text/plain", err.Error()) + return + } + if c := form.Get("cnt"); len(c) > 0 { + counter, _ = strconv.ParseInt(string(c), 0, 0) + } + } + + var body [32]byte + b := append(body[:0], `{"cnt": `...) + b = strconv.AppendInt(b, counter, 10) + b = append(b, '}') + ex.Respond(httphi.StatusOK, "application/json", b) +} diff --git a/examples/net/webstatic-httphi/images/tinygo-logo.png b/examples/net/webstatic-httphi/images/tinygo-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d362856fd97eb03e150096cce163d1ef2a116c04 GIT binary patch literal 30898 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 + + TinyGo - A Go Compiler For Small Places + + + +

TinyGo - A Go Compiler For Small Places

+ + + diff --git a/examples/net/webstatic-httphi/main.go b/examples/net/webstatic-httphi/main.go new file mode 100644 index 000000000..cd45b32de --- /dev/null +++ b/examples/net/webstatic-httphi/main.go @@ -0,0 +1,123 @@ +// This example is the webstatic example using the heapless httphi router +// from github.com/soypat/lneto instead of "net/http". + +//go:build ninafw || wioterminal + +package main + +import ( + "embed" + "log" + "net" + "path" + "time" + + "github.com/soypat/lneto/http/httphi" + + "tinygo.org/x/drivers/netlink" + "tinygo.org/x/drivers/netlink/probe" +) + +var ( + ssid string + pass string + port string = ":80" +) + +const ( + numWorkers = 2 + memoryPerConn = 1024 +) + +//go:embed index.html main.go images +var fs embed.FS + +func main() { + // wait a bit for console + time.Sleep(2 * time.Second) + + link, _ := probe.Probe() + + err := link.NetConnect(&netlink.ConnectParams{ + Ssid: ssid, + Passphrase: pass, + }) + if err != nil { + log.Fatal(err) + } + + var mux httphi.MuxSlice + mux.Handle("GET /", serveFile) + + var router httphi.Router + cfg := httphi.DefaultRouterConfig(numWorkers, memoryPerConn, mux.MaxPathValues()) + err = router.Configure(&mux, cfg) + if err != nil { + log.Fatal(err) + } + + listener, err := net.Listen("tcp", port) + if err != nil { + log.Fatal(err) + } + + for { + conn, err := listener.Accept() + if err != nil { + println("accept:", err.Error()) + time.Sleep(time.Second) + continue + } + if err := router.Handle(conn); err != nil { + conn.Close() + } + } +} + +func serveFile(ex *httphi.Exchange) { + name := string(ex.RequestPath()[1:]) + if name == "" { + name = "index.html" + } + + f, err := fs.Open(name) + if err != nil { + ex.RespondString(httphi.StatusNotFound, "text/plain", "404 page not found") + return + } + defer f.Close() + + info, err := f.Stat() + if err != nil || info.IsDir() { + ex.RespondString(httphi.StatusNotFound, "text/plain", "404 page not found") + return + } + + ex.StageHeader("Content-Type", contentType(name)) + ex.StageHeaderInt("Content-Length", info.Size()) + ex.StageHeader("Connection", "close") + + var buf [512]byte + for { + n, err := f.Read(buf[:]) + if n > 0 { + if _, werr := ex.WriteBody(buf[:n]); werr != nil { + return + } + } + if err != nil { + return + } + } +} + +func contentType(name string) string { + switch path.Ext(name) { + case ".html": + return "text/html; charset=utf-8" + case ".png": + return "image/png" + default: + return "text/plain; charset=utf-8" + } +} diff --git a/go.mod b/go.mod index 432c77ae0..9ce9b8054 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,13 @@ module tinygo.org/x/drivers -go 1.22.1 - -toolchain go1.23.1 +go 1.24 require ( github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/frankban/quicktest v1.10.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/orsinium-labs/tinymath v1.1.0 + github.com/soypat/lneto v0.4.0 github.com/soypat/natiu-mqtt v0.5.1 github.com/tinygo-org/pio v0.3.0 golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d diff --git a/go.sum b/go.sum index 673d984d8..8a889e8e4 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTeV1vS5UA= github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A= +github.com/soypat/lneto v0.4.0 h1:oBTcMnhEIBERXmAfwK4uDpItRaF7RiT1K2pljFVxtQc= +github.com/soypat/lneto v0.4.0/go.mod h1:Be5PjwoYukvHFiUXxpYi8+ppH2F/gw/vjGBvFdv+Ti8= github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc= github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4= github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI= diff --git a/smoketest.go b/smoketest.go index 174526986..6219308ec 100644 --- a/smoketest.go +++ b/smoketest.go @@ -97,6 +97,7 @@ func runSmokeTest(filename string) error { _ = flagSet.String("size", "", "") _ = flagSet.String("o", "", "") _ = flagSet.String("stack-size", "", "") + _ = flagSet.String("tags", "", "") targetFlag := flagSet.String("target", "", "") err = flagSet.Parse(fields[2:]) if err != nil { diff --git a/smoketest.sh b/smoketest.sh index b9f2c4995..8fdb530c8 100755 --- a/smoketest.sh +++ b/smoketest.sh @@ -176,11 +176,14 @@ tinygo build -size short -o ./build/test.hex -target=arduino-nano33 -stack-size tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb ./examples/net/websocket/dial/ tinygo build -size short -o ./build/test.hex -target=metro-m4-airlift -stack-size 8kb ./examples/net/socket/ tinygo build -size short -o ./build/test.hex -target=matrixportal-m4 -stack-size 8kb ./examples/net/webstatic/ +tinygo build -size short -o ./build/test.hex -tags=noslog -target=matrixportal-m4 -stack-size 8kb ./examples/net/webstatic-httphi/ +tinygo build -size short -o ./build/test.hex -tags=noslog -target=arduino-nano33 -stack-size 4kb ./examples/net/webserver-httphi/ tinygo build -size short -o ./build/test.hex -target=arduino-mkrwifi1010 -stack-size 8kb ./examples/net/tlsclient/ tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb ./examples/net/mqttclient/natiu/ # network examples (rtl8720dn) tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/webclient/ tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/webserver/ +tinygo build -size short -o ./build/test.hex -tags=noslog -target=wioterminal -stack-size 8kb ./examples/net/webserver-httphi/ tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/mqttclient/paho/ # network examples (comboat) tinygo build -size short -o ./build/test.hex -target=elecrow-rp2040 -stack-size 8kb ./examples/net/tlsclient/