p2p: Use JSServer for comms rather than GoJsConn (#888)
* p2p: Use JSServer for comms rather than GoJsConn This has several benefits: - it fixes a bug whereby you could not transmit >4k bytes to/from JS/Go land. - it more clearly exposes the interface point between Go and JS: a single global function call. - it presents a nicer API shape than the previous `net.Conn`. - it doesn't needlessly 'stream' data which is already sitting in-memory. This is currently only active for local CS API traffic, another PR will add Federation P2P support. * Typo
This commit is contained in:
parent
d71b72816d
commit
8bc5084d8d
5 changed files with 118 additions and 15 deletions
104
cmd/dendritejs/jsServer.go
Normal file
104
cmd/dendritejs/jsServer.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build wasm
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// JSServer exposes an HTTP-like server interface which allows JS to 'send' requests to it.
|
||||
type JSServer struct {
|
||||
// The router which will service requests
|
||||
Mux *http.ServeMux
|
||||
}
|
||||
|
||||
// OnRequestFromJS is the function that JS will invoke when there is a new request.
|
||||
// The JS function signature is:
|
||||
// function(reqString: string): Promise<{result: string, error: string}>
|
||||
// Usage is like:
|
||||
// const res = await global._go_js_server.fetch(reqString);
|
||||
// if (res.error) {
|
||||
// // handle error: this is a 'network' error, not a non-2xx error.
|
||||
// }
|
||||
// const rawHttpResponse = res.result;
|
||||
func (h *JSServer) OnRequestFromJS(this js.Value, args []js.Value) interface{} {
|
||||
// we HAVE to spawn a new goroutine and return immediately or else Go will deadlock
|
||||
// if this request blocks at all e.g for /sync calls
|
||||
httpStr := args[0].String()
|
||||
promise := js.Global().Get("Promise").New(js.FuncOf(func(pthis js.Value, pargs []js.Value) interface{} {
|
||||
// The initial callback code for new Promise() is also called on the critical path, which is why
|
||||
// we need to put this in an immediately invoked goroutine.
|
||||
go func() {
|
||||
resolve := pargs[0]
|
||||
fmt.Println("Received request:")
|
||||
fmt.Printf("%s\n", httpStr)
|
||||
resStr, err := h.handle(httpStr)
|
||||
errStr := ""
|
||||
if err != nil {
|
||||
errStr = err.Error()
|
||||
}
|
||||
fmt.Println("Sending response:")
|
||||
fmt.Printf("%s\n", resStr)
|
||||
resolve.Invoke(map[string]interface{}{
|
||||
"result": resStr,
|
||||
"error": errStr,
|
||||
})
|
||||
}()
|
||||
return nil
|
||||
}))
|
||||
return promise
|
||||
}
|
||||
|
||||
// handle invokes the http.ServeMux for this request and returns the raw HTTP response.
|
||||
func (h *JSServer) handle(httpStr string) (resStr string, err error) {
|
||||
req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(httpStr)))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.Mux.ServeHTTP(w, req)
|
||||
|
||||
res := w.Result()
|
||||
var resBuffer strings.Builder
|
||||
err = res.Write(&resBuffer)
|
||||
return resBuffer.String(), err
|
||||
}
|
||||
|
||||
// ListenAndServe registers a variable in JS-land with the given namespace. This variable is
|
||||
// a function which JS-land can call to 'send' HTTP requests. The function is attached to
|
||||
// a global object called "_go_js_server". See OnRequestFromJS for more info.
|
||||
func (h *JSServer) ListenAndServe(namespace string) {
|
||||
globalName := "_go_js_server"
|
||||
// register a hook in JS-land for it to invoke stuff
|
||||
server := js.Global().Get(globalName)
|
||||
if !server.Truthy() {
|
||||
server = js.Global().Get("Object").New()
|
||||
js.Global().Set(globalName, server)
|
||||
}
|
||||
|
||||
server.Set(namespace, js.FuncOf(h.OnRequestFromJS))
|
||||
|
||||
fmt.Printf("Listening for requests from JS on function %s.%s\n", globalName, namespace)
|
||||
// Block forever to mimic http.ListenAndServe
|
||||
select {}
|
||||
}
|
|
@ -156,10 +156,10 @@ func main() {
|
|||
// Expose the matrix APIs via fetch - for local traffic
|
||||
go func() {
|
||||
logrus.Info("Listening for service-worker fetch traffic")
|
||||
|
||||
listener := go_http_js_libp2p.NewFetchListener()
|
||||
s := &http.Server{}
|
||||
go s.Serve(listener)
|
||||
s := JSServer{
|
||||
Mux: http.DefaultServeMux,
|
||||
}
|
||||
s.ListenAndServe("fetch")
|
||||
}()
|
||||
|
||||
// We want to block forever to let the fetch and libp2p handler serve the APIs
|
||||
|
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
|||
github.com/lib/pq v1.2.0
|
||||
github.com/libp2p/go-libp2p-core v0.5.0
|
||||
github.com/matrix-org/dugong v0.0.0-20171220115018-ea0a4690a0d5
|
||||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200306192008-b9e71eeaa437
|
||||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200310180544-7f3fad43b51c
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20200304164012-aa524245b658
|
||||
github.com/matrix-org/gomatrix v0.0.0-20190528120928-7df988a63f26
|
||||
github.com/matrix-org/gomatrixserverlib v0.0.0-20200306154041-df6bb9a3e424
|
||||
|
|
2
go.sum
2
go.sum
|
@ -122,6 +122,8 @@ github.com/matrix-org/go-http-js-libp2p v0.0.0-20200306190227-af44d7013315 h1:tE
|
|||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200306190227-af44d7013315/go.mod h1:/giSXVd8D6DZGSfTmhQrLEoZZwsfkC14kSqP9MiLqIY=
|
||||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200306192008-b9e71eeaa437 h1:zcGpWvVV6swXw9LBMRsdDHPOugQYSwesH2RByUfBx2I=
|
||||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200306192008-b9e71eeaa437/go.mod h1:/giSXVd8D6DZGSfTmhQrLEoZZwsfkC14kSqP9MiLqIY=
|
||||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200310180544-7f3fad43b51c h1:jj/LIZKMO7GK6O0UarpRwse9L3ZyzozpyMtdPA7ddSk=
|
||||
github.com/matrix-org/go-http-js-libp2p v0.0.0-20200310180544-7f3fad43b51c/go.mod h1:qK3LUW7RCLhFM7gC3pabj3EXT9A1DsCK33MHstUhhbk=
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20200226144546-ea6ed5b90074 h1:UWz6vfhmQVshBuE67X1BCsdMhEDtd+uOz8CJ48Fc0F4=
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20200226144546-ea6ed5b90074/go.mod h1:e+cg2q7C7yE5QnAXgzo512tgFh1RbQLC0+jozuegKgo=
|
||||
github.com/matrix-org/go-sqlite3-js v0.0.0-20200304163011-cfb4884075db h1:ERuFJq4DI8fakfBZlvXHltHZ0ix3K5YsLG0tQfQn6TI=
|
||||
|
|
17
p2p.md
17
p2p.md
|
@ -16,7 +16,7 @@ $ cp main.wasm ../riot-web/src/vector/dendrite.wasm
|
|||
|
||||
This is how peers discover each other and communicate.
|
||||
|
||||
By default, Dendrite uses the IPFS-hosted websocket star **Development** relay server at `/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star`.
|
||||
By default, Dendrite uses the Matrix-hosted websocket star relay server at TODO `/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star`.
|
||||
This is currently hard-coded in `./cmd/dendritejs/main.go` - you can also use a local one if you run your own relay:
|
||||
|
||||
```
|
||||
|
@ -24,13 +24,13 @@ $ npm install --global libp2p-websocket-star-rendezvous
|
|||
$ rendezvous --port=9090 --host=127.0.0.1
|
||||
```
|
||||
|
||||
Then use `/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star/`. We'll probably run our own relay server at some point.
|
||||
Then use `/ip4/127.0.0.1/tcp/9090/ws/p2p-websocket-star/`.
|
||||
|
||||
### Riot-web
|
||||
|
||||
You need to check out these repos:
|
||||
|
||||
``
|
||||
```
|
||||
$ git clone git@github.com:matrix-org/go-http-js-libp2p.git
|
||||
$ git clone git@github.com:matrix-org/go-sqlite3-js.git
|
||||
```
|
||||
|
@ -39,6 +39,7 @@ Make sure to `yarn install` in both of these repos. Then:
|
|||
|
||||
- `$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./src/vector/`
|
||||
- Comment out the lines in `wasm_exec.js` which contains:
|
||||
|
||||
```
|
||||
if (!global.fs && global.require) {
|
||||
global.fs = require("fs");
|
||||
|
@ -56,17 +57,13 @@ NB: If you don't run the server with `yarn start` you need to make sure your ser
|
|||
|
||||
TODO: Make a Docker image with all of this in it and a volume mount for `dendrite.wasm`.
|
||||
|
||||
## Running
|
||||
### Running
|
||||
|
||||
You need a Chrome and a Firefox running to test locally as service workers don't work in incognito tabs.
|
||||
- For Chrome, use `chrome://serviceworker-internals/` to unregister/see logs.
|
||||
- For Firefox, use `about:debugging#/runtime/this-firefox` to unregister. Use the console window to see logs.
|
||||
|
||||
Assuming you've `yarn start`ed Riot-Web, go to `http://localhost:8080` and wait a bit. Then refresh the page (this is required
|
||||
because the fetch interceptor races with setting up dendrite. If you don't refresh, you won't be able to contact your HS). After
|
||||
the refresh, click Register and use `http://localhost:8080` as your HS URL.
|
||||
|
||||
TODO: Fix the race so we don't need multiple refreshes.
|
||||
Assuming you've `yarn start`ed Riot-Web, go to `http://localhost:8080` and register with `http://localhost:8080` as your HS URL.
|
||||
|
||||
You can join rooms by room alias e.g `/join #foo:bar`.
|
||||
|
||||
|
@ -74,7 +71,7 @@ You can join rooms by room alias e.g `/join #foo:bar`.
|
|||
|
||||
- When registering you may be unable to find the server, it'll seem flakey. This happens because the SW, particularly in Firefox,
|
||||
gets killed after 30s of inactivity. When you are not registered, you aren't doing `/sync` calls to keep the SW alive, so if you
|
||||
don't register for a while and idle on the page, the HS will disappear. To fix, unregister the SW, and then refresh the page *twice*.
|
||||
don't register for a while and idle on the page, the HS will disappear. To fix, unregister the SW, and then refresh the page.
|
||||
|
||||
- The libp2p layer has rate limits, so frequent Federation traffic may cause the connection to drop and messages to not be transferred.
|
||||
I guess in other words, don't send too much traffic?
|
||||
|
|
Loading…
Reference in a new issue