Skip to content
This repository has been archived by the owner on Jun 5, 2024. It is now read-only.

Add prscd p2p server #90

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions prscd/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
build/
bin/
dist/
.vscode/
test_pages/
prscd.pid
55 changes: 55 additions & 0 deletions prscd/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
GO ?= go
GOFMT ?= gofmt "-s"
GOFILES := $(shell find . -name "*.go")
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /example/)

.PHONY: fmt
fmt:
$(GOFMT) -w $(GOFILES)

.PHONY: vet
vet:
$(GO) vet $(VETPACKAGES)

.PHONY: lint
lint:
revive -exclude chirp/*_test.go -exclude cmd/prscd/epoll.go -formatter friendly ./...

.PHONY: build
build:
$(GO) build -o bin/prscd ./cmd/prscd

.PHONY: dist
dist: clean
GOOS=linux GOARCH=amd64 $(GO) build -ldflags "-s -w" -o dist/prscd-x86_64-linux ./cmd/prscd
GOOS=linux GOARCH=arm64 $(GO) build -ldflags "-s -w" -o dist/prscd-arm64-linux ./cmd/prscd
GOOS=darwin GOARCH=amd64 $(GO) build -ldflags "-s -w" -o dist/prscd-x86_64-darwin ./cmd/prscd
GOOS=darwin GOARCH=arm64 $(GO) build -ldflags "-s -w" -o dist/prscd-arm64-darwin ./cmd/prscd
GOOS=windows GOARCH=amd64 $(GO) build -ldflags "-s -w" -o dist/prscd-x86_64-windows.exe ./cmd/prscd
GOOS=windows GOARCH=arm64 $(GO) build -ldflags "-s -w" -o dist/prscd-arm64-windows.exe ./cmd/prscd

.PHONY: dev
dev:
$(GO) run -race ./cmd/prscd

.PHONY: test
test:
MESH_ID=test go test ./...

.PHONY: bench
bench:
MESH_ID=bench LOG_LEVEL=2 go test -bench=. -benchmem yomo.run/prscd/chirp

.PHONY: testpage
testpage:
@mkdir -p ./test_pages
@cp msgpack.js ./test_pages
@cp websocket.html test_pages/.
@sed -i '' 's/URL_DEBG/URL_PROD/g' test_pages/websocket.html
@cp webtrans.html test_pages/.
@sed -i '' 's/URL_DEBG/URL_PROD/g' test_pages/webtrans.html

.PHONY: clean
clean:
@rm -rf dist
@rm -rf bin
141 changes: 141 additions & 0 deletions prscd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@

# 🌎 Prscd

The open source backend for Presencejs v2.0

## 🎯 Roadmap

- [x] Websocket arraybuffer support
- [x] Zero-copy upgrade to WebSocket
- [x] SO_REUSEPORT on Darwin and Linux
- [x] Implement WebSocket native Ping/Pong frame to keep alive
- [ ] reuse goroutine
- [x] WebTransport Datagram support, unreliable but fast communication
- [ ] WebTransport Stream support, reliable
- [ ] pprof
- [x] Prscd Clusters by YoMo
- [x] Geo-distributed System / Distributed Cloud arch by YoMo

## 🥷🏻 Development

1. Start prscd service in terminal-2:`make dev`
1. Open `webtransport.html` by Chrome with Dev Tools
1. Open `websocket.html` by Chrome with Dev Tools

![](https://github.com/fanweixiao/gifs-repo/blob/main/prscd-readme.gif)

[![asciicast](https://asciinema.org/a/565542.svg)](https://asciinema.org/a/565542)

## 🦸🏻 Self-hosting

Compile:

```bash
make dist
```

### ☝🏻 Host on Single Cloud Region

TODO: how to deploy `prscd` on digitalocean

TODO: introducing [geoping.gg](https://geoping.gg), lighthouse for realtime applications.

### 🌍 Host as Geo-distributed System

DNS improvements explaination

#### by Vercel edge functions

Redirect end-user connect to node close to them.

#### by AWS Global Accelarator

Anycast IP

#### by Azure Traffic Manager

Geo-IP

## ☕️ FAQ

### about https://lo.yomo.dev

```bash
$ openssl x509 -enddate -noout -in prscd/lo.yomo.dev.cert
notAfter=May 22 07:40:45 2023 GMT
```

### how to generate SSL for your own domain

1. `brew install certbot`
2. `sudo certbot certonly --manual --preferred-challenges dns -d prscd.example.com`
3. create a TXT record followed the instruction by certbot
4. `nslookup -type=TXT _acme-challenge.prscd.example.com` to verify the process
5. `sudo chown -Rv "$(whoami)":staff /etc/letsencrypt/` set permission
6. cert and key: `/etc/letsencrypt/live/prscd.example.com/{fullchain, privkey}.pem`
7. verify the expiratioin time: `openssl x509 -enddate -noout -in prscd.example.com.cert.pem`

### if you are behind a proxy on Mac

Most of proxy applications drop WebTransport or HTTP/3, so if you are a macos user,
this bash script can helped bypass `*.yomo.dev` domain to proxy.

```bash
networksetup -setproxybypassdomains "Wi-Fi" $(networksetup -getproxybypassdomains "Wi-Fi" | awk '{ printf "\"%s\" ", $0 }') "*.yomo.dev"
```

### Integrate to your own Auth system

Currently, provide `public_key` for authentication, the endpoint looks like: `/v1?app_id=<USER_CLIENT_ID>&public_key=<PUBLIC_KEY>`

### Live inspection

Execute `make dev` in terminal-1:

```bash
$ make dev
go run -race main.go
pid: 20079
Listening SIGUSR1, SIGUSR2, SIGTERM/SIGINT...
```

Open terminal-2, execute:

```bash
$ kill -SIGUSR1 20079
$ kill -SIGUSR2 20079
```

The output of terminal-1 will looks like:

```bash
$ make dev
go run -race main.go
pid: 20079
Listening SIGUSR1, SIGUSR2, SIGTERM/SIGINT...
Received signal: user defined signal 1
SIGUSR1
Dump start --------
Peers: 1
Channel:room-1
Peer:127.0.0.1:62577
Dump doen --------
Received signal: user defined signal 2
NumGC = 0
```

### Configure Firewall of Cloud Provider

TCP and UDP on the `PORT` shall has to be allowed in security rules.

## .env File

- `DEBUG=true`: debug mode
- `PORT=443`: indicate the PORT used to listen, both WebSocket and WebTransport
- `MESH_ID=MID_EAST`: indicate nodes in distributed cloud archtecture
- `YOMO_SNDR_NAME`: the name of YoMo Source
- `YOMO_RCVR_NAME`: the name of YoMo Stream Function
- `CERT_FILE`: The SSL cert file path of prscd
- `YOMO_TRACE_JAEGER_ENDPOINT`: Jaeger collector endpoint, e.g., http://localhost:14268/api/traces

- `KEY_FILE`: The SSL key file path of prscd
69 changes: 69 additions & 0 deletions prscd/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash

set -e

# prscd build script for Linux
# Environment variable options:
# - PRSCD_VERSION: App version
# - PRSCD_PLATFORMS: Platforms to build for (e.g. "windows/amd64,linux/amd64,darwin/amd64")

export LC_ALL=C
export LC_DATE=C

make_ldflags() {
local ldflags="-s -w -X 'main.appDate=$(date -u '+%F %T')'"
if [ -n "$PRSCD_VERSION" ]; then
ldflags="$ldflags -X 'main.appVersion=$PRSCD_VERSION'"
else
ldflags="$ldflags -X 'main.appVersion=$(git describe --tags --always --match 'v*')'"
fi
echo "$ldflags"
}

build_for_platform() {
local platform="$1"
local ldflags="$2"

local GOOS="${platform%/*}"
local GOARCH="${platform#*/}"
if [[ -z "$GOOS" || -z "$GOARCH" ]]; then
echo "Invalid platform $platform" >&2
return 1
fi
echo "Building $GOOS/$GOARCH"
local output="build/prscd"
if [[ "$GOOS" = "windows" ]]; then
output="$output.exe"
fi
# compress to .zip file
local binfile="build/prscd-$GOARCH-$GOOS.zip"
local exit_val=0
GOOS=$GOOS GOARCH=$GOARCH go build -o "$output" -ldflags "$ldflags" -trimpath || exit_val=$?
# compress compiled binary to .zip
zip -r -j "$binfile" "$output"
rm -rf $output
if [[ "$exit_val" -ne 0 ]]; then
echo "Error: failed to build $GOOS/$GOARCH" >&2
return $exit_val
fi
}


if [ -z "$PRSCD_PLATFORMS" ]; then
PRSCD_PLATFORMS="$(go env GOOS)/$(go env GOARCH)"
fi
platforms=(${PRSCD_PLATFORMS//,/ })
ldflags="$(make_ldflags)"

mkdir -p build
rm -rf build/*

echo "Starting build..."

for platform in "${platforms[@]}"; do
build_for_platform "$platform" "$ldflags"
done

echo "Build complete."

ls -lh build/ | awk '{print $9, $5}'
79 changes: 79 additions & 0 deletions prscd/chirp/channel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Package chirp describes peer-to-peer communication protocol.
package chirp

import (
"sync"

"github.com/vmihailenco/msgpack/v5"
"github.com/yomorun/psig"
"yomo.run/prscd/util"
)

// Channel describes a message channel.
type Channel struct {
UniqID string // uniq id
AppID string // TODO: APP_ID
pdic sync.Map // all peers subscribed this channel
}

// AddPeer add peer to this channel.
func (c *Channel) AddPeer(p *Peer) {
c.pdic.Store(p.Sid, p)
}

// RemovePeer remove peer from this channel.
func (c *Channel) RemovePeer(p *Peer) {
c.pdic.Delete(p.Sid)
}

// Broadcast message to all peers in this channel by yomo,
// yomo create a distributed cloud network, peers from different location
// will connect to different nodes in this network, so the message will be
// broadcast to all nodes.
func (c *Channel) Broadcast(sig *psig.Signalling) {
sigSentOverYoMo := sig.Clone()
sigSentOverYoMo.AppID = c.AppID
sigSentOverYoMo.MeshID = Node.MeshID
go Node.BroadcastToYoMo(&sigSentOverYoMo)
}

// Dispatch messages to all peers in this channel of current node.
func (c *Channel) Dispatch(sig *psig.Signalling) {
// sig.Sid is sender's sid when sending message
log.Debug("[%s]\tSND>: %+v", sig.Sid, sig)
var sender = sig.Sid
// do not broadcast APP_ID and Sid to end user
sig.AppID = ""
sig.Sid = ""
resp, err := msgpack.Marshal(sig)
if err != nil {
log.Error("msgpack marshal: %+v", err)
return
}

c.pdic.Range(func(k, v interface{}) bool {
// do not broadcast to sender-self
sid := k.(string)
p := v.(*Peer)
if sid == sender {
util.Log.Debug("-----------ignore sender-self: %s", sender)
return true
}
util.Log.Debug("[%s] BroadcastPresence to ch:%s, for sid:%s", sender, c.UniqID, p.Sid)
err = p.conn.Write(resp)
if err != nil {
log.Error("ws.write error: %+v", err)
}
return true
})
}

// getLen returns the number of peers in this channel of current node.
func (c *Channel) getLen() int {
var count int
c.pdic.Range(func(k, v interface{}) bool {
count++
return true
})
return count
}
Loading
Loading