Skip to content

Commit ce6765e

Browse files
jphastingsnlepage
authored andcommitted
feat: Demonstrates TinyGo compatibility
Adds an example that demonstrates TinyGo compatibility, as well as using a server-side HTTP handler as a fallback.
1 parent d12a255 commit ce6765e

File tree

10 files changed

+204
-5
lines changed

10 files changed

+204
-5
lines changed

README.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [Hello example with state and keepalive](https://nlepage.github.io/go-wasm-http-server/hello-state-keepalive) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state-keepalive))
2121
- [😺 Catption generator example](https://nlepage.github.io/catption/wasm) ([sources](https://github.com/nlepage/catption/tree/wasm))
2222
- [Random password generator web server](https://nlepage.github.io/random-password-please/) ([sources](https://github.com/nlepage/random-password-please) forked from [jbarham/random-password-please](https://github.com/jbarham/random-password-please))
23+
- [Server fallbacks, and compiling with TinyGo](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/tinygo) (runs locally; see [sources & readme](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/tinygo#README) for how to run this example)
2324

2425

2526
## How?
@@ -39,6 +40,7 @@ The slides are available [here](https://nlepage.github.io/go-wasm-http-talk/).
3940
`go-wasm-http-server` requires you to build your Go application to WebAssembly, so you need to make sure your code is compatible:
4041
- no C bindings
4142
- no System dependencies such as file system or network (database server for example)
43+
- For smaller WASM blobs, your code may also benefit from being compatible with, and compiled by, [TinyGo](https://tinygo.org/docs/reference/lang-support/stdlib/). See the TinyGo specific details below.
4244

4345
## Usage
4446

@@ -87,16 +89,36 @@ You may want to use build tags as shown above (or file name suffixes) in order t
8789
Then build your WebAssembly binary:
8890

8991
```sh
92+
# To compile with Go
9093
GOOS=js GOARCH=wasm go build -o server.wasm .
94+
95+
# To compile with TinyGo, if your code is compatible
96+
GOOS=js GOARCH=wasm tinygo build -o server.wasm .
9197
```
9298

9399
### Step 2: Create ServiceWorker file
94100

101+
First, check the version of Go/TinyGo you compiled your wasm with:
102+
103+
```sh
104+
$ go version
105+
go version go1.23.4 darwin/arm64
106+
# ^------^
107+
108+
$ tinygo version
109+
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
110+
# ^----^
111+
```
112+
95113
Create a ServiceWorker file with the following code:
96114

97115
📄 `sw.js`
98116
```js
99-
importScripts('https://cdn.jsdelivr.net/gh/golang/[email protected]/misc/wasm/wasm_exec.js')
117+
// Note the 'go.1.23.4' below, that matches the version you just found:
118+
importScripts('https://cdn.jsdelivr.net/gh/golang/[email protected]/misc/wasm/wasm_exec.js')
119+
// If you compiled with TinyGo then, similarly, use:
120+
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/[email protected]/targets/wasm_exec.js')
121+
100122
importScripts('https://cdn.jsdelivr.net/gh/nlepage/[email protected]/sw.js')
101123

102124
registerWasmHTTPListener('path/to/server.wasm')

docs/tinygo/README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Compiling with TinyGo
2+
3+
This example demonstrates that go-wasm-http-server can also be compiled with [TinyGo](https://www.tinygo.org), producing significantly smaller WASM blobs, though at the expense of [at least one known bug](https://github.com/tinygo-org/tinygo/issues/1140) and a [reduced standard library](https://tinygo.org/docs/reference/lang-support/stdlib/).
4+
5+
This example also demonstrates how the same code can be used for both server-side execution, and client-side execution in WASM (providing support for clients that cannot interpret WASM).
6+
7+
## Prerequisites
8+
9+
You'll need a version of [TinyGo installed](https://tinygo.org/getting-started/install/). (eg. `brew install tinygo-org/tools/tinygo`)
10+
11+
You'll need to make sure the first line of `sw.js` here has the same tinygo version number as your TinyGo version (this was v0.35.0 at time of writing).
12+
13+
## Build & run
14+
15+
Compile the WASM blob with TinyGo (this has been done for you for this example):
16+
17+
```bash
18+
GOOS=js GOARCH=wasm tinygo build -o api.wasm .
19+
```
20+
21+
Run the server (with Go, not TinyGo):
22+
23+
```bash
24+
$ go run .
25+
Server starting on http://127.0.0.1:<port>
26+
```
27+
28+
## Important notes
29+
30+
You **must** use the TinyGo `wasm_exec.js`, specific to the version of TinyGo used to compile the WASM, in your `sw.js`. For example, if using the JSDelivr CDN:
31+
32+
```js
33+
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/[email protected]/targets/wasm_exec.js')
34+
```
35+
36+
Note that the `0.35.0` within the path matches the TinyGo version used:
37+
38+
```sh
39+
$ tinygo version
40+
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
41+
# ^----^
42+
```

docs/tinygo/api.wasm

1.57 MB
Binary file not shown.

docs/tinygo/handlers.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"runtime"
7+
)
8+
9+
func goRuntimeHandler(res http.ResponseWriter, req *http.Request) {
10+
res.Header().Add("Content-Type", "application/json")
11+
if err := json.NewEncoder(res).Encode(map[string]string{
12+
"os": runtime.GOOS,
13+
"arch": runtime.GOARCH,
14+
"compiler": runtime.Compiler,
15+
"version": runtime.Version(),
16+
}); err != nil {
17+
panic(err)
18+
}
19+
}

docs/tinygo/index.html

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>go-wasm-http-server tinygo demo</title>
5+
<script>
6+
const sw = navigator.serviceWorker
7+
8+
function attachServiceWorker() {
9+
sw.register('sw.js')
10+
.then(() => {
11+
document.getElementById('wasm-status').innerText = "⚡️ Loaded - Will execute WASM locally"
12+
})
13+
.catch((err) => {
14+
document.getElementById('wasm-status').innerText = "🛑 Error loading service worker — Check console"
15+
console.error(err)
16+
})
17+
}
18+
19+
async function makeQuery() {
20+
const res = await fetch('api/tiny', {
21+
method: 'POST',
22+
headers: {
23+
'Content-Type': 'application/json'
24+
}
25+
})
26+
27+
document.getElementById('output').innerText = await res.text()
28+
}
29+
</script>
30+
</head>
31+
<body>
32+
<p>This example demonstrates that go-wasm-http-server can be compiled with <a href="https://www.tinygo.org">TinyGo</em>, producing significantly smaller WASM blobs, at the expense of <a href="https://github.com/tinygo-org/tinygo/issues/1140">at least one known bug</a>, and a <a href="https://tinygo.org/docs/reference/lang-support/stdlib/">reduced standard library</a>.</p>
33+
<dl><dt>WASM HTTP Service Worker:</dt><dd id="wasm-status">☁️ Not loaded — will call server</dd></dl>
34+
35+
<ol>
36+
<li><button onclick="makeQuery()">Call API</button></li>
37+
<li><button onclick="attachServiceWorker()">Attach the service worker</button></li>
38+
<li><span>Call the API again (Step 1)</span></li>
39+
</ol>
40+
41+
<h3>Response:</h3>
42+
<pre id="output"></pre>
43+
</body>
44+
</html>

docs/tinygo/server.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//go:build !wasm
2+
// +build !wasm
3+
4+
package main
5+
6+
import (
7+
"embed"
8+
"fmt"
9+
"log"
10+
"net"
11+
"net/http"
12+
)
13+
14+
//go:embed *.html *.js *.wasm
15+
var thisDir embed.FS
16+
17+
func main() {
18+
// Serve all files in this directory statically
19+
http.Handle("/", http.FileServer(http.FS(thisDir)))
20+
21+
// Note that this needs to be mounted at /api/tiny, rather than just /tiny (like in wasm.go)
22+
// because the service worker mounts the WASM server at /api (at the end of sw.js)
23+
http.HandleFunc("/api/tiny", goRuntimeHandler)
24+
25+
// Pick any available port. Note that ServiceWorkers _require_ localhost for non-SSL serving (so other LAN/WAN IPs will prevent the service worker from loading)
26+
listener, err := net.Listen("tcp", ":0")
27+
if err != nil {
28+
log.Fatalf("Unable to claim a port to start server on: %v", err)
29+
}
30+
31+
// Share the port being used & start
32+
fmt.Printf("Server starting on http://127.0.0.1:%d\n", listener.Addr().(*net.TCPAddr).Port)
33+
panic(http.Serve(listener, nil))
34+
}

docs/tinygo/sw.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/[email protected]/targets/wasm_exec.js')
2+
importScripts('https://cdn.jsdelivr.net/gh/nlepage/[email protected]/sw.js')
3+
4+
const wasm = 'api.wasm'
5+
6+
addEventListener('install', (event) => {
7+
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
8+
})
9+
10+
addEventListener('activate', (event) => {
11+
event.waitUntil(clients.claim())
12+
})
13+
14+
registerWasmHTTPListener(wasm, { base: 'api' })

docs/tinygo/wasm.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//go:build wasm
2+
// +build wasm
3+
4+
package main
5+
6+
import (
7+
"net/http"
8+
9+
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
10+
)
11+
12+
func main() {
13+
http.HandleFunc("/tiny", goRuntimeHandler)
14+
15+
wasmhttp.Serve(nil)
16+
17+
select {}
18+
}

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182
44
github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=
55
github.com/tmaxmax/go-sse v0.8.0 h1:pPpTgyyi1r7vG2o6icebnpGEh3ebcnBXqDWkb7aTofs=
66
github.com/tmaxmax/go-sse v0.8.0/go.mod h1:HLoxqxdH+7oSUItjtnpxjzJedfr/+Rrm/dNWBcTxJFM=
7+
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
8+
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9+
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=

request.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,13 @@ func Request(uvalue js.Value) (*http.Request, error) {
6464
}
6565

6666
req := &http.Request{
67-
Method: method,
68-
URL: u,
69-
Body: bodyReader,
70-
Header: make(http.Header),
67+
Method: method,
68+
URL: u,
69+
Body: bodyReader,
70+
Header: make(http.Header),
71+
Proto: "HTTP/1.1",
72+
ProtoMajor: 1,
73+
ProtoMinor: 1,
7174
}
7275

7376
headers, err := value.Get("headers")

0 commit comments

Comments
 (0)