mirror of
https://github.com/nlepage/go-wasm-http-server.git
synced 2025-03-10 09:27:08 +00:00
Merge pull request #17 from jphastings/compile-with-tinygo
TinyGo compatible packages
This commit is contained in:
commit
8e787fbf29
24
README.md
24
README.md
@ -20,6 +20,7 @@
|
||||
- [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))
|
||||
- [😺 Catption generator example](https://nlepage.github.io/catption/wasm) ([sources](https://github.com/nlepage/catption/tree/wasm))
|
||||
- [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))
|
||||
- [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)
|
||||
|
||||
|
||||
## How?
|
||||
@ -39,6 +40,7 @@ The slides are available [here](https://nlepage.github.io/go-wasm-http-talk/).
|
||||
`go-wasm-http-server` requires you to build your Go application to WebAssembly, so you need to make sure your code is compatible:
|
||||
- no C bindings
|
||||
- no System dependencies such as file system or network (database server for example)
|
||||
- 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.
|
||||
|
||||
## Usage
|
||||
|
||||
@ -87,16 +89,36 @@ You may want to use build tags as shown above (or file name suffixes) in order t
|
||||
Then build your WebAssembly binary:
|
||||
|
||||
```sh
|
||||
# To compile with Go
|
||||
GOOS=js GOARCH=wasm go build -o server.wasm .
|
||||
|
||||
# To compile with TinyGo, if your code is compatible
|
||||
GOOS=js GOARCH=wasm tinygo build -o server.wasm .
|
||||
```
|
||||
|
||||
### Step 2: Create ServiceWorker file
|
||||
|
||||
First, check the version of Go/TinyGo you compiled your wasm with:
|
||||
|
||||
```sh
|
||||
$ go version
|
||||
go version go1.23.4 darwin/arm64
|
||||
# ^------^
|
||||
|
||||
$ tinygo version
|
||||
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
|
||||
# ^----^
|
||||
```
|
||||
|
||||
Create a ServiceWorker file with the following code:
|
||||
|
||||
📄 `sw.js`
|
||||
```js
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
|
||||
// Note the 'go.1.23.4' below, that matches the version you just found:
|
||||
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.23.4/misc/wasm/wasm_exec.js')
|
||||
// If you compiled with TinyGo then, similarly, use:
|
||||
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
|
||||
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
|
||||
|
||||
registerWasmHTTPListener('path/to/server.wasm')
|
||||
|
42
docs/tinygo/README.md
Normal file
42
docs/tinygo/README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Compiling with TinyGo
|
||||
|
||||
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/).
|
||||
|
||||
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).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
You'll need a version of [TinyGo installed](https://tinygo.org/getting-started/install/). (eg. `brew install tinygo-org/tools/tinygo`)
|
||||
|
||||
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).
|
||||
|
||||
## Build & run
|
||||
|
||||
Compile the WASM blob with TinyGo (this has been done for you for this example):
|
||||
|
||||
```bash
|
||||
GOOS=js GOARCH=wasm tinygo build -o api.wasm .
|
||||
```
|
||||
|
||||
Run the server (with Go, not TinyGo):
|
||||
|
||||
```bash
|
||||
$ go run .
|
||||
Server starting on http://127.0.0.1:<port>
|
||||
```
|
||||
|
||||
## Important notes
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
|
||||
```
|
||||
|
||||
Note that the `0.35.0` within the path matches the TinyGo version used:
|
||||
|
||||
```sh
|
||||
$ tinygo version
|
||||
tinygo version 0.35.0 darwin/arm64 (using go version go1.23.4 and LLVM version 18.1.2)
|
||||
# ^----^
|
||||
```
|
BIN
docs/tinygo/api.wasm
Normal file
BIN
docs/tinygo/api.wasm
Normal file
Binary file not shown.
19
docs/tinygo/handlers.go
Normal file
19
docs/tinygo/handlers.go
Normal file
@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func goRuntimeHandler(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(res).Encode(map[string]string{
|
||||
"os": runtime.GOOS,
|
||||
"arch": runtime.GOARCH,
|
||||
"compiler": runtime.Compiler,
|
||||
"version": runtime.Version(),
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
44
docs/tinygo/index.html
Normal file
44
docs/tinygo/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>go-wasm-http-server tinygo demo</title>
|
||||
<script>
|
||||
const sw = navigator.serviceWorker
|
||||
|
||||
function attachServiceWorker() {
|
||||
sw.register('sw.js')
|
||||
.then(() => {
|
||||
document.getElementById('wasm-status').innerText = "⚡️ Loaded - Will execute WASM locally"
|
||||
})
|
||||
.catch((err) => {
|
||||
document.getElementById('wasm-status').innerText = "🛑 Error loading service worker — Check console"
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
async function makeQuery() {
|
||||
const res = await fetch('api/tiny', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('output').innerText = await res.text()
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<dl><dt>WASM HTTP Service Worker:</dt><dd id="wasm-status">☁️ Not loaded — will call server</dd></dl>
|
||||
|
||||
<ol>
|
||||
<li><button onclick="makeQuery()">Call API</button></li>
|
||||
<li><button onclick="attachServiceWorker()">Attach the service worker</button></li>
|
||||
<li><span>Call the API again (Step 1)</span></li>
|
||||
</ol>
|
||||
|
||||
<h3>Response:</h3>
|
||||
<pre id="output"></pre>
|
||||
</body>
|
||||
</html>
|
34
docs/tinygo/server.go
Normal file
34
docs/tinygo/server.go
Normal file
@ -0,0 +1,34 @@
|
||||
//go:build !wasm
|
||||
// +build !wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed *.html *.js *.wasm
|
||||
var thisDir embed.FS
|
||||
|
||||
func main() {
|
||||
// Serve all files in this directory statically
|
||||
http.Handle("/", http.FileServer(http.FS(thisDir)))
|
||||
|
||||
// Note that this needs to be mounted at /api/tiny, rather than just /tiny (like in wasm.go)
|
||||
// because the service worker mounts the WASM server at /api (at the end of sw.js)
|
||||
http.HandleFunc("/api/tiny", goRuntimeHandler)
|
||||
|
||||
// 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)
|
||||
listener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to claim a port to start server on: %v", err)
|
||||
}
|
||||
|
||||
// Share the port being used & start
|
||||
fmt.Printf("Server starting on http://127.0.0.1:%d\n", listener.Addr().(*net.TCPAddr).Port)
|
||||
panic(http.Serve(listener, nil))
|
||||
}
|
14
docs/tinygo/sw.js
Normal file
14
docs/tinygo/sw.js
Normal file
@ -0,0 +1,14 @@
|
||||
importScripts('https://cdn.jsdelivr.net/gh/tinygo-org/tinygo@0.35.0/targets/wasm_exec.js')
|
||||
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v2.0.5/sw.js')
|
||||
|
||||
const wasm = 'api.wasm'
|
||||
|
||||
addEventListener('install', (event) => {
|
||||
event.waitUntil(caches.open('examples').then((cache) => cache.add(wasm)))
|
||||
})
|
||||
|
||||
addEventListener('activate', (event) => {
|
||||
event.waitUntil(clients.claim())
|
||||
})
|
||||
|
||||
registerWasmHTTPListener(wasm, { base: 'api' })
|
18
docs/tinygo/wasm.go
Normal file
18
docs/tinygo/wasm.go
Normal file
@ -0,0 +1,18 @@
|
||||
//go:build wasm
|
||||
// +build wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
wasmhttp "github.com/nlepage/go-wasm-http-server/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/tiny", goRuntimeHandler)
|
||||
|
||||
wasmhttp.Serve(nil)
|
||||
|
||||
select {}
|
||||
}
|
3
go.sum
3
go.sum
@ -4,3 +4,6 @@ github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182
|
||||
github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=
|
||||
github.com/tmaxmax/go-sse v0.8.0 h1:pPpTgyyi1r7vG2o6icebnpGEh3ebcnBXqDWkb7aTofs=
|
||||
github.com/tmaxmax/go-sse v0.8.0/go.mod h1:HLoxqxdH+7oSUItjtnpxjzJedfr/+Rrm/dNWBcTxJFM=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
|
@ -14,7 +14,7 @@ type Reader struct {
|
||||
off int
|
||||
}
|
||||
|
||||
var _ io.Reader = (*Reader)(nil)
|
||||
var _ io.ReadCloser = (*Reader)(nil)
|
||||
|
||||
func NewReader(r safejs.Value) *Reader {
|
||||
return &Reader{
|
||||
@ -83,3 +83,14 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Close() error {
|
||||
p, err := r.value.Call("cancel")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = promise.Await(safejs.Unsafe(p))
|
||||
|
||||
return err
|
||||
}
|
||||
|
24
request.go
24
request.go
@ -3,7 +3,7 @@ package wasmhttp
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"syscall/js"
|
||||
|
||||
promise "github.com/nlepage/go-js-promise"
|
||||
@ -20,7 +20,11 @@ func Request(uvalue js.Value) (*http.Request, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url, err := value.GetString("url")
|
||||
rawURL, err := value.GetString("url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -30,7 +34,7 @@ func Request(uvalue js.Value) (*http.Request, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
var bodyReader io.ReadCloser
|
||||
|
||||
if !body.IsNull() {
|
||||
// WORKAROUND: Firefox does not have request.body ReadableStream
|
||||
@ -59,11 +63,15 @@ func Request(uvalue js.Value) (*http.Request, error) {
|
||||
bodyReader = readablestream.NewReader(r)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(
|
||||
method,
|
||||
url,
|
||||
bodyReader,
|
||||
)
|
||||
req := &http.Request{
|
||||
Method: method,
|
||||
URL: u,
|
||||
Body: bodyReader,
|
||||
Header: make(http.Header),
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
}
|
||||
|
||||
headers, err := value.Get("headers")
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user