12 Commits

Author SHA1 Message Date
Nicolas Lepage
163b49702b ⬆️ go 1.18 compatibility 2022-07-28 00:08:56 +02:00
Nicolas Lepage
624ed00220 ♻️ Use github.com/nlepage/go-js-promise 2021-08-16 23:40:53 +02:00
Nicolas Lepage
1f549a4bf0 📝 Create funding.yml 2021-03-02 12:07:53 +01:00
Nicolas Lepage
73a09847ca ✏️ 2021-02-18 22:01:34 +01:00
Nicolas Lepage
0bf86b9d79 📝 2021-02-10 12:44:50 +01:00
Nicolas Lepage
167237a124 📝 2021-02-09 23:20:40 +01:00
Nicolas Lepage
f602159c47 📝 2021-02-09 23:18:59 +01:00
Nicolas Lepage
a06a85731f 📝 New Random password generator web server example 2021-02-08 13:54:49 +01:00
Nicolas Lepage
17e34981b0 📝 2021-02-06 18:15:36 +01:00
Nicolas Lepage
7434774930 📝 2021-01-31 22:24:30 +01:00
Nicolas Lepage
d2e039bd3e 📝 2021-01-31 21:59:22 +01:00
Nicolas Lepage
76abf72cff 📝 2021-01-28 16:00:27 +01:00
16 changed files with 176 additions and 97 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: [nlepage]

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"go.toolsEnvVars": {
"GOOS": "js",
"GOARCH": "wasm"
}
}

162
README.md
View File

@@ -11,25 +11,159 @@
</a>
</p>
> Build your Go HTTP Server to [WebAssembly](https://mdn.io/WebAssembly/) and embed it in a ServiceWorker!
> Embed your Go HTTP handlers in a ServiceWorker (using [WebAssembly](https://mdn.io/WebAssembly/)) and emulate an HTTP server!
✨ [Demos](https://nlepage.github.io/go-wasm-http-server/)
## Examples
## Install
- [Hello example](https://nlepage.github.io/go-wasm-http-server/hello) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello))
- [Hello example with state](https://nlepage.github.io/go-wasm-http-server/hello-state) ([sources](https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state))
- [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))
TODO
## Usage
TODO
## Why?
TODO
## How?
TODO
Talk given at the Go devroom of FOSDEM 2021 explaining how `go-wasm-http-server` works:
[![Deploy a Go HTTP server in your browser Youtube link](https://raw.githubusercontent.com/nlepage/go-wasm-http-talk/main/youtube.png)](https://youtu.be/O2RB_8ircdE)
The slides are available [here](https://nlepage.github.io/go-wasm-http-talk/).
## Why?
`go-wasm-http-server` can help you put up a demonstration for a project without actually running a Go HTTP server.
## Requirements
`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)
## Usage
### Step 1: Build to `js/wasm`
In your Go code, replace [`http.ListenAndServe()`](https://pkg.go.dev/net/http#ListenAndServe) (or [`net.Listen()`](https://pkg.go.dev/net#Listen) + [`http.Serve()`](https://pkg.go.dev/net/http#Serve)) by [wasmhttp.Serve()](https://pkg.go.dev/github.com/nlepage/go-wasm-http-server#Serve):
📄 `server.go`
```go
// +build !js,!wasm
package main
import (
"net/http"
)
func main() {
// Define handlers...
http.ListenAndServe(":8080", nil)
}
```
becomes:
📄 `server_js_wasm.go`
```go
// +build js,wasm
package main
import (
wasmhttp "github.com/nlepage/go-wasm-http-server"
)
func main() {
// Define handlers...
wasmhttp.Serve(nil)
}
```
You may want to use build tags as shown above (or file name suffixes) in order to be able to build both to WebAssembly and other targets.
Then build your WebAssembly binary:
```sh
GOOS=js GOARCH=wasm go build -o server.wasm .
```
### Step 2: Create ServiceWorker file
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')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
registerWasmHTTPListener('path/to/server.wasm')
```
By default the server will deploy at the ServiceWorker's scope root, check [`registerWasmHTTPListener()`'s API](https://github.com/nlepage/go-wasm-http-server#registerwasmhttplistenerwasmurl-options) for more information.
You may want to add these additional event listeners in your ServiceWorker:
```js
// Skip installed stage and jump to activating stage
addEventListener('install', (event) => {
event.waitUntil(skipWaiting())
})
// Start controlling clients as soon as the SW is activated
addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
```
### Step 3: Register the ServiceWorker
In your web page(s), register the ServiceWorker:
```html
<script>
// By default the ServiceWorker's scope will be "server/"
navigator.serviceWorker.register('server/sw.js')
</script>
```
Now your web page(s) may start fetching from the server:
```js
// The server will receive a request for "/path/to/resource"
fetch('server/path/to/resource').then(res => {
// use response...
})
```
## API
For Go API see [pkg.go.dev/github.com/nlepage/go-wasm-http-server](https://pkg.go.dev/github.com/nlepage/go-wasm-http-server#section-documentation)
### JavaScript API
### [`registerWasmHTTPListener(wasmUrl, options)`](https://github.com/nlepage/go-wasm-http-server/blob/v1.0.0/sw.js#L3)
Instantiates and runs the WebAssembly module at `wasmUrl`, and registers a fetch listener forwarding requests to the WebAssembly module's server.
⚠ This function must be called only once in a ServiceWorker, if you want to register several servers you must use several ServiceWorkers.
The server will be "deployed" at the root of the ServiceWorker's scope by default, `base` may be used to deploy the server at a subpath of the scope.
See [ServiceWorkerContainer.register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register) for more information about the scope of a ServiceWorker.
#### `wasmUrl`
URL string of the WebAssembly module, example: `"path/to/my-module.wasm"`.
#### `options`
An optional object containing:
- `base` (`string`): Base path of the server, relative to the ServiceWorker's scope.
- `args` (`string[]`): Arguments for the WebAssembly module.
## Author
@@ -53,4 +187,4 @@ Copyright © 2021 [Nicolas Lepage](https://github.com/nlepage).<br />
This project is [Apache 2.0](https://github.com/nlepage/go-wasm-http-server/blob/master/LICENSE) licensed.
***
_This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_
_This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_

View File

@@ -1,4 +1,5 @@
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@078ff3547ebe2abfbee1fd5af9ca5ad64be480c0/sw.js')
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
addEventListener('install', event => {
event.waitUntil(skipWaiting())

Binary file not shown.

View File

@@ -1,4 +1,5 @@
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@078ff3547ebe2abfbee1fd5af9ca5ad64be480c0/sw.js')
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
addEventListener('install', (event) => {
event.waitUntil(skipWaiting())

Binary file not shown.

View File

@@ -1,9 +1,10 @@
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@078ff3547ebe2abfbee1fd5af9ca5ad64be480c0/sw.js')
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.18.4/misc/wasm/wasm_exec.js')
importScripts('https://cdn.jsdelivr.net/gh/nlepage/go-wasm-http-server@v1.1.0/sw.js')
addEventListener('install', (event) => {
event.waitUntil(skipWaiting())
})
addEventListener('activate', event => {
event.waitUntil(clients.claim())
})

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>go-wasm-http-server demo</title>
</head>
<body>
<ul>
<li><a href="hello">Hello demo</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello">sources</a>)</li>
<li><a href="hello-state">Hello with state demo</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state">sources</a>)</li>
<li><a href="hello-state-keepalive">Hello with state and keepalive demo</a> (<a href="https://github.com/nlepage/go-wasm-http-server/tree/master/docs/hello-state-keepalive">sources</a>)</li>
<li><a href="https://nlepage.github.io/catption/wasm/">😺 Catption generator demo</a> (<a href="https://github.com/nlepage/catption/tree/wasm">sources</a>)</li>
</ul>
</body>
</html>

2
go.mod
View File

@@ -1,3 +1,5 @@
module github.com/nlepage/go-wasm-http-server
go 1.13
require github.com/nlepage/go-js-promise v1.0.0

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/nlepage/go-js-promise v1.0.0 h1:K7OmJ3+0BgWJ2LfXchg2sI6RDr7AW/KWR8182epFwGQ=
github.com/nlepage/go-js-promise v1.0.0/go.mod h1:bdOP0wObXu34euibyK39K1hoBCtlgTKXGc56AGflaRo=

View File

@@ -1,55 +0,0 @@
package wasmhttp
import (
"syscall/js"
)
// NewPromise creates a new JavaScript Promise
func NewPromise() (p js.Value, resolve func(interface{}), reject func(interface{})) {
var cbFunc js.Func
cbFunc = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
cbFunc.Release()
resolve = func(value interface{}) {
args[0].Invoke(value)
}
reject = func(value interface{}) {
args[1].Invoke(value)
}
return js.Undefined()
})
p = js.Global().Get("Promise").New(cbFunc)
return
}
// Await waits for the Promise to be resolved and returns the value
func Await(p js.Value) (js.Value, error) {
resCh := make(chan js.Value)
var then js.Func
then = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
resCh <- args[0]
return nil
})
defer then.Release()
errCh := make(chan error)
var catch js.Func
catch = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
errCh <- js.Error{args[0]}
return nil
})
defer catch.Release()
p.Call("then", then).Call("catch", catch)
select {
case res := <-resCh:
return res, nil
case err := <-errCh:
return js.Undefined(), err
}
}

View File

@@ -5,11 +5,13 @@ import (
"net/http"
"net/http/httptest"
"syscall/js"
promise "github.com/nlepage/go-js-promise"
)
// Request builds and returns the equivalent http.Request
func Request(r js.Value) *http.Request {
jsBody := js.Global().Get("Uint8Array").New(Await(r.Call("arrayBuffer")))
jsBody := js.Global().Get("Uint8Array").New(promise.Await(r.Call("arrayBuffer")))
body := make([]byte, jsBody.Get("length").Int())
js.CopyBytesToGo(body, jsBody)

View File

@@ -6,7 +6,7 @@ import (
"syscall/js"
)
// ResponseRecorder extends httptest.ResponseRecorder and implements js.Wrapper
// ResponseRecorder uses httptest.ResponseRecorder to build a JS Response
type ResponseRecorder struct {
*httptest.ResponseRecorder
}
@@ -16,10 +16,8 @@ func NewResponseRecorder() ResponseRecorder {
return ResponseRecorder{httptest.NewRecorder()}
}
var _ js.Wrapper = ResponseRecorder{}
// JSValue builds and returns the equivalent JS Response (implementing js.Wrapper)
func (rr ResponseRecorder) JSValue() js.Value {
// JSResponse builds and returns the equivalent JS Response
func (rr ResponseRecorder) JSResponse() js.Value {
var res = rr.Result()
var body js.Value = js.Undefined()

View File

@@ -5,6 +5,8 @@ import (
"net/http"
"strings"
"syscall/js"
promise "github.com/nlepage/go-js-promise"
)
// Serve serves HTTP requests using handler or http.DefaultServeMux if handler is nil.
@@ -26,7 +28,7 @@ func Serve(handler http.Handler) func() {
}
var cb = js.FuncOf(func(_ js.Value, args []js.Value) interface{} {
var resPromise, resolve, reject = NewPromise()
var resPromise, resolve, reject = promise.New()
go func() {
defer func() {
@@ -43,7 +45,7 @@ func Serve(handler http.Handler) func() {
h.ServeHTTP(res, Request(args[0]))
resolve(res)
resolve(res.JSResponse())
}()
return resPromise

2
sw.js
View File

@@ -1,5 +1,3 @@
importScripts('https://cdn.jsdelivr.net/gh/golang/go@go1.15.7/misc/wasm/wasm_exec.js')
function registerWasmHTTPListener(wasm, { base, args = [] } = {}) {
let path = new URL(registration.scope).pathname
if (base && base !== '') path = `${trimEnd(path, '/')}/${trimStart(base, '/')}`