This commit is contained in:
wyf5229 2020-09-10 16:08:06 +08:00
commit b4a7f4e53f
10 changed files with 1106 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.exe
.idea

58
cert.crt Normal file
View File

@ -0,0 +1,58 @@
-----BEGIN CERTIFICATE-----
MIIEeDCCAmCgAwIBAgIIEtc4Hk5jNvIwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE
BhMCQ04xDzANBgNVBAcTBlN1emhvdTEQMA4GA1UECgwHRXZhbl9DQTEQMA4GA1UE
CwwHRXZhbl9DQTEQMA4GA1UEAwwHRXZhbl9DQTAeFw0yMDA5MDMwMTA1MDBaFw0y
MTA5MDMwMTA1MDBaMBkxFzAVBgNVBAMMDioubWlyaWxsaXMuY29tMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwcf0gqkGW79baJ39995n1YirD4x5KtnC
3CsD1gftE/va3GZMToBsP3jdohi5TRvB/CnF6Ngv22kszDWTwLKg/K73D+5dfu8m
xVyxx2kU4uzC0NR0Z2AtumWIS05N/S53PWESBbCgPBJLcGKqLULYXf9EvEPtXebh
i68C4aGnCmWOcMCLH/VrCcwFT15eR6zsD3whv8Oqpdj5aneItpEaWd8dzBrHO69k
byfqK/GL14eLtPClVoY0EQ8z6hNqNTil6eseBdEuwytND5LJnfzarXCBxdnh74CE
u4nxZcgEIx3hoSpLJVtHiZfGXPZgtIech3LgcAcAmcaI6wtD94Z5pQIDAQABo4GI
MIGFMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFK5Br4x8HWHZOIAYSdYxEj1JW/TF
MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
JwYDVR0RBCAwHoIOKi5taXJpbGxpcy5jb22CDG1pcmlsbGlzLmNvbTANBgkqhkiG
9w0BAQsFAAOCAgEARlaPieyqlQgp9xsMd6Qc59Wx90NJF8BbkXOxxP9A84Pf93D/
oYhSWJQepDl6Q9aw4BuXBYMOrBJiOpMwbm1cv8YdCiNZUAS3mOwhgffBUwcJPnGt
/YjOulNDYu9sq09MlS6MALMZs7Dqfzh3RHzaWLsSEXZrsFUw2ONCKxjCT33Zv30v
1uKpWONRRcfPQV/b72uYrJf+ByX7gYZKSssolj1aq3qtxBitZR05EV4iWCPBp7tz
y0wQcz9+vbfE9tdo+VECuD8Rldl770VE6GZnL9/jAwLsv8VB+9z7jlvbcfwdatvv
FYE1dSPkw2eQq0/urrFv2thqISKv/qO8SsHYnXfA6gbHuz8WHH6DYc57ArLAO329
5bdVEn5hL/rWxUx/QipjCmtlIBG1eqUSfdAKOWK3PVUC3NqwoUSBoNLxJ9xU9rJB
7xdOEP0TdXyCOhaiLYCv3FoHRfgTwrtHiSoi3hyHftxjwKGtyrFo1rS3FTdLHret
IRWIzIV7NP9gGYNZaCG+V2fQqxOFjH+lR7hvrOHP+oX2M8D6rnIPWvJtZLpnxIyF
kDPS2ch0TR5UtohXiGfO5mx/m4CVKcrcRTJGVQSkGv0VZ1cHSvxZayGgTfkOIUBu
YPHJeQBt6agjGzAUOxHzRa4xdyYIUMC6/nv8SMAIPTUEBq1GvE4kPmB48MI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFfDCCA2SgAwIBAgIIYMu9eb5hc/gwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE
BhMCQ04xDzANBgNVBAcTBlN1emhvdTEQMA4GA1UECgwHRXZhbl9DQTEQMA4GA1UE
CwwHRXZhbl9DQTEQMA4GA1UEAwwHRXZhbl9DQTAeFw0xOTExMjgwNjM3MDBaFw0y
OTExMjgwNjM3MDBaMFQxCzAJBgNVBAYTAkNOMQ8wDQYDVQQHEwZTdXpob3UxEDAO
BgNVBAoMB0V2YW5fQ0ExEDAOBgNVBAsMB0V2YW5fQ0ExEDAOBgNVBAMMB0V2YW5f
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDRsAGo/do3sqN4IQjt
QGfCebwA/DZT1xRDPbo/AuxB7KprpKEW+7sP5PnFUAavMRyX7gs9fCYS0+ed1rnx
/bZkGE3ARKR9njlCTHBQbAEBdKXL7OgqDnnvTmG2HBlS1id87TLBdKijxoCmVDoB
Y/KXPTzfBvmAW+h79h6lK05eecne0BIlq5nFuI77HZYckvspZ4KBn9STE1Sg+xRw
9RmjFFI9Dzvnd/SkvmRicCvR4h5NNviIqQp12ulUUHwzAHmbpq9HQT/9ddsrhymt
zrZJYLlflDZTAhvAalcICbNFhmA/koBPu9x7xjACK1ylkvcmLCu+vIq563lmrmgg
yYBxgSk55WFPFMxoOviDKva7kcr+OoFCu1iDGSNxS4UAnxDRxHiy+RNkLIIRTXU6
87C8q2cwdUm8jmJiuOwEUwUdeiyNk4VyvLWNGzcTQSyz5vkijlwSpwKPDFnvRivY
6CX8Avj2ep3MXd8OVO4e2xdrxVpC7N2rTJG8m+uLeHly0MJbGoGMZrEfFvuQiCRA
dp90yOr+wioVuUqdnWWSY3h8o3Hxh0nU4RsDG3DE/e10s2TVEEWfJFt/0v0GIfki
0nNa/pqQRyr2rp/G+nC97xYqJDUr+mzKkUn1oqyZ2CBfmpIkC9A7Ex2RsT5IDpDu
vOIEY4nXXpcEsCO94OGgcAkMgwIDAQABo1IwUDAPBgNVHRMBAf8EBTADAQH/MB0G
A1UdDgQWBBR475cDo1XAKOtfI0l+07cgFoO3MzALBgNVHQ8EBAMCAQYwEQYJYIZI
AYb4QgEBBAQDAgAHMA0GCSqGSIb3DQEBCwUAA4ICAQCSD3MVSmIgPYYVL2RlZTAg
05ywU+jKs20xhMtzErDodWpIoUDbkeNsXACXsUOfruPJs2ey/yro4+7ioSaDeIrA
tGiNhBuZq9cIgX2vM9Dro0w/8HEQxZEBOyRqRMfbj6oFo5k5nr1ruc2kQzuFxp2p
bi19IoeIb4+rGIFE+d2HKqPKfIbwFv3X9+arrVsEWF2MV5WVsGOGV4dX+aCQ6V3D
XlvZAUg+ru2OQZ0LgCfJNzXa7wGLGC5IUGVaHEuquL+6TowTsAoN0/Jdpo2Et6jN
hRcb4rwrdvBA0bvubPjfIIVXjymb5hqbX5hdUCI8csFY3QjxoM4hPgxZmkCoxv9R
KmUo/vIsR1ZC/t2rnZjAeEMOS/F+mNt465Arab15Xw4SMd0ZXA2C058RElwGZSxO
OriiOZNCaHxGA6dCHnwFSAlQ0hJyOtPcF0VpcGLSpSESXffX3qD4miQi9HWcLdph
OlUN/Txqb6Jov6MvAbHMaCvsUE3Qdm8LxFBOn9/SPlcBveApbqSsH6Ac5xhG5tnU
A3BR6X/cn9bQveZggNZtMV/NRyqQ0F3oA9cCW7WEWuQWTse+0eLWg9eloKx+pDR4
6qVuHGtQeqvy+fBPInjovk9REo6GoH9qM8BAWWuhOv+v85K+MSybvIBA/LM0/uvF
a2PBtACsbvJx70oiDxZ1yw==
-----END CERTIFICATE-----

14
common.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"bytes"
"encoding/json"
)
func formatJson(v interface{}) []byte {
marshal, _ := json.Marshal(v)
var b bytes.Buffer
json.Indent(&b, marshal, "", " ")
return b.Bytes()
}

25
login.go Normal file
View File

@ -0,0 +1,25 @@
package main
type LoginRequest struct {
Login string `json:"login"`
Password string `json:"password"`
Fingerprint string `json:"fingerprint"`
}
type LoginResponse struct {
CreatedAt int64 `json:"created_at,omitempty"` //1599031125
ApiKey string `json:"apikey,omitempty"` //"apikey-af461547-da12-5e33-9a99-c63d9000762b"
MacAddress string `json:"mac_address,omitempty"` //"04ea56b3f380"
Metadata string `json:"metadata,omitempty"` //"{\"os\":\"1\",\"state\":\"online\",\"streamer\":\"0\",\"type\":\"2\"}"
Fingerprint string `json:"fingerprint,omitempty"` //"fingerprint": "45c9dd6c-6925-a994-3fc3-c26cdec786ce",
Id string `json:"id,omitempty"` // "id": "device-4d4c9fba-4d11-5b10-b3b3-05346a7bd4d2",
Client string `json:"client,omitempty"` //"client": "client-8c220f6e-3432-5f41-80f2-b830f7b60658",
Name string `json:"name,omitemptys"` //"name": "DESKTOP-3H0CLPE"
}
type AddDeviceRequest struct {
Name string `json:"name"` //DESKTOP-3H0CLPE
MacAddress string `json:"mac_address"` //04ea56b3f380
Fingerprint string `json:"fingerprint"`
Metadata string `json:"metadata"`
}

251
management.go Normal file
View File

@ -0,0 +1,251 @@
package main
import (
"github.com/gorilla/websocket"
uuid "github.com/satori/go.uuid"
"math/rand"
"strconv"
"strings"
"time"
)
type MonfloManagement struct {
clients []*ClientInfo
}
type ClientInfo struct {
UserName string `json:"user_name"`
Metadata string `json:"metadata"`
Name string `json:"name"`
PrivateAddr string `json:"private_addr"`
PublicAddr string `json:"public_addr"`
Key string `json:"key"`
Bitrate int `json:"bitrate"`
Formats []string `json:"formats"` //h264 h265
Stream string `json:"stream"`
ClientStatus string `json:"client_status"` //online offline
StreamStatus string `json:"stream_status"` //ready busy
CreatedAt int64 `json:"created_at"` //1599031125
ApiKey string `json:"apikey"` //"apikey-af461547-da12-5e33-9a99-c63d9000762b"
MacAddress string `json:"mac_address"` //"04ea56b3f380"
Fingerprint string `json:"fingerprint"` //"fingerprint": "45c9dd6c-6925-a994-3fc3-c26cdec786ce",
Id string `json:"id"` // "id": "device-4d4c9fba-4d11-5b10-b3b3-05346a7bd4d2",
Client string `json:"client"` //"client": "client-8c220f6e-3432-5f41-80f2-b830f7b60658",
wsConn *websocket.Conn
StreamId string `json:"stream_id"` //40832596627
ClientType string `json:"client_type"` //client server
}
func (m *MonfloManagement) checkExist(userName string, fingerprint string) bool {
if m.clients == nil {
return false
}
for _, client := range m.clients {
if client.Fingerprint == fingerprint && client.UserName == userName {
return true
}
}
return false
}
func (m *MonfloManagement) getClient(userName string, fingerprint string) *ClientInfo {
for _, client := range m.clients {
if client.Fingerprint == fingerprint && client.UserName == userName {
return client
}
}
return nil
}
func (m *MonfloManagement) getClientByApiKey(apiKey string) *ClientInfo {
for _, client := range m.clients {
if client.ApiKey == apiKey {
return client
}
}
return nil
}
func (m *MonfloManagement) getClientByFingerprint(fingerprint string) *ClientInfo {
for _, client := range m.clients {
if client.Fingerprint == fingerprint {
return client
}
}
return nil
}
func (m *MonfloManagement) addClient(userName string, fingerprint string) {
if !m.checkExist(userName, fingerprint) {
client := &ClientInfo{
UserName: userName,
CreatedAt: time.Now().Unix(),
ApiKey: "apikey-" + fingerprint,
Fingerprint: fingerprint,
Id: "device-" + fingerprint,
Client: "client-" + fingerprint,
ClientStatus: "offline",
}
m.clients = append(m.clients, client)
}
}
func (m *MonfloManagement) addServer(clientStr string) *ClientInfo {
for _, c := range m.clients {
if c.Client == clientStr {
return c
}
}
client := &ClientInfo{
Client: clientStr,
CreatedAt: time.Now().Unix(),
}
m.clients = append(m.clients, client)
return client
}
func (m *MonfloManagement) addDevice(request AddDeviceRequest) {
client := m.getClientByFingerprint(request.Fingerprint)
client.Name = request.Name
client.Fingerprint = request.Fingerprint
client.Metadata = request.Metadata
client.MacAddress = request.MacAddress
if strings.Contains(request.Metadata, "\"streamer\":\"1\",") {
client.ClientType = "server"
} else {
client.ClientType = "client"
}
}
func (m *MonfloManagement) getPeers(userName string) map[string]PeerInfo {
peers := make(map[string]PeerInfo)
if m.clients == nil {
return peers
}
for _, client := range m.clients {
if client.UserName == userName {
streams := make(map[string]StreamInfo)
var formats = []string{"h264", "h265"}
if client.Stream != "" {
streams[client.Stream] = StreamInfo{
Id: client.Stream,
Status: client.StreamStatus,
OwnerClient: client.Client,
Name: client.Name,
PrivateAddr: client.PrivateAddr,
PublicAddr: client.PublicAddr,
Key: client.Key,
Bitrate: 500000,
Formats: formats,
}
}
peers[client.Client] = PeerInfo{
Name: client.Name,
Status: client.ClientStatus, //online;offline
LastIP: IPInfo{
PrivateAddr: client.PrivateAddr,
PublicAddr: client.PublicAddr,
},
Streams: streams,
}
}
}
return peers
}
func (m *MonfloManagement) getDevices(userName string) []LoginResponse {
var devices []LoginResponse
if m.clients == nil {
return devices
}
for _, client := range m.clients {
if client.UserName == userName {
device := LoginResponse{
CreatedAt: client.CreatedAt,
ApiKey: client.ApiKey,
MacAddress: client.MacAddress,
Metadata: client.Metadata,
Fingerprint: client.Fingerprint,
Id: client.Id,
Client: client.Client,
Name: client.Name,
}
devices = append(devices, device)
}
}
return devices
}
func (m *MonfloManagement) genId(client *ClientInfo) {
id := 10000000009 + 8*rand.Int63n(5)
for {
if !m.checkIdExist(strconv.FormatInt(id, 10)) {
break
}
id = 10000000009 + 8*rand.Int63n(10000)
}
client.StreamId = strconv.FormatInt(id, 10)
}
func (m *MonfloManagement) genStream(client *ClientInfo) {
uid, _ := uuid.NewV4()
client.Stream = "stream-" + uid.String()
client.StreamStatus = "ready"
}
func (m *MonfloManagement) checkIdExist(id string) bool {
for _, c := range m.clients {
if c.Id == id {
return true
}
}
return false
}
func (m *MonfloManagement) setOffline(clientId string) {
del := -1
for i, c := range m.clients {
if c.Client == clientId {
if c.ApiKey == "" && c.Id == "" {
del = i
}
c.ClientStatus = "offline"
}
}
if del >= 0 {
m.clients = append(m.clients[:del], m.clients[del+1:]...)
}
}
func (m *MonfloManagement) getClientStream(stream string) *ClientInfo {
for _, c := range m.clients {
if c.Stream == stream {
return c
}
}
return nil
}
func (m *MonfloManagement) getClientStreamId(streamId string) *ClientInfo {
for _, c := range m.clients {
if c.StreamId == streamId {
return c
}
}
return nil
}
func (m *MonfloManagement) getUserAllClient(userName string) []*ClientInfo {
var clients []*ClientInfo
for _, c := range m.clients {
if c.UserName == userName {
clients = append(clients, c)
}
}
return clients
}

583
monflo.go Normal file
View File

@ -0,0 +1,583 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
uuid "github.com/satori/go.uuid"
"io/ioutil"
"math/rand"
"net"
"net/http"
"strings"
"sync"
"time"
)
var (
upgrader = websocket.Upgrader{
//允许跨域访问
CheckOrigin: func(r *http.Request) bool {
return true
},
EnableCompression: false,
}
managementService = MonfloManagement{}
)
func main() {
rand.Seed(time.Now().UnixNano())
go stun()
http.HandleFunc("/2015-10-26/verification", verification)
http.HandleFunc("/2015-10-26/devices", device)
http.HandleFunc("/2015-10-26", wsHandler)
http.HandleFunc("/config", configHandler)
http.HandleFunc("/set", configSetHandler)
http.HandleFunc("/peer", peerHandler)
err := http.ListenAndServeTLS("", "cert.crt", "private.key", nil)
if err != nil {
fmt.Print(err)
}
}
func configHandler(w http.ResponseWriter, r *http.Request) {
marshal, _ := json.Marshal(managementService.clients)
w.Write(marshal)
}
func configSetHandler(w http.ResponseWriter, r *http.Request) {
var clients []*ClientInfo
data, _ := ioutil.ReadAll(r.Body)
json.Unmarshal(data, &clients)
managementService.clients = clients
}
func peerHandler(w http.ResponseWriter, r *http.Request) {
peers := managementService.getPeers(r.FormValue("username"))
marshal, _ := json.Marshal(peers)
w.Write(marshal)
}
func stun() {
address := "0.0.0.0:3478"
addr, err := net.ResolveUDPAddr("udp", address)
if err != nil {
fmt.Println(err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
fmt.Println(err)
}
defer conn.Close()
for {
// Here must use make and give the lenth of buffer
data := make([]byte, 4096)
_, rAddr, err := conn.ReadFromUDP(data)
if err != nil {
fmt.Println(err)
continue
}
//file, _ := ioutil.ReadFile("data.bin")
//_, err = conn.WriteToUDP(file, rAddr)
_, err = conn.WriteToUDP([]byte{254, 239, 1, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 6, 58, 210, 98, 46, 174, 198, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, rAddr)
if err != nil {
fmt.Println(err)
continue
}
}
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
var (
wsConn *websocket.Conn
err error
mutex sync.Mutex
client *ClientInfo
)
//Upgrade websocket(返回给客户端的消息)
if wsConn, err = upgrader.Upgrade(w, r, nil); err != nil {
//报错了直接返回底层的websocket链接就会终断掉
return
}
go func() {
for {
mutex.Lock()
err2 := wsConn.WriteMessage(websocket.PingMessage, []byte{})
mutex.Unlock()
if err2 != nil {
wsConn.Close()
return
}
time.Sleep(1 * time.Second)
}
defer func() {
if r := recover(); r != nil {
fmt.Printf("Runtime error caught: %v", r)
}
}()
}()
defer func() {
if r := recover(); r != nil {
fmt.Printf("Runtime error caught: %v", r)
}
if nil != client {
managementService.setOffline(client.Client)
}
}()
mutex.Lock()
wsConn.WriteMessage(websocket.TextMessage, []byte("{\n \"event\": \"cookie_created\",\n \"data\": \"5f0389ae63c5c5962de0bf1fa7edbb9e7605da388a8ffb2f69af4c47b1fde020\"\n}"))
mutex.Unlock()
client = new(ClientInfo)
for {
messageType, p, err := wsConn.ReadMessage()
if err != nil {
fmt.Printf("WebSocket异常断开," + wsConn.RemoteAddr().String() + ",异常信息:" + err.Error() + "\n")
wsConn.Close()
return
}
if messageType == websocket.CloseMessage {
fmt.Printf("WebSocket断开," + wsConn.RemoteAddr().String() + "\n")
wsConn.Close()
return
}
fmt.Printf(string(p) + "\n")
mutex.Lock()
tempClient := wsProcess(wsConn, p, client)
mutex.Unlock()
if tempClient != nil {
client = tempClient
}
}
}
func wsProcess(wsConn *websocket.Conn, data []byte, client *ClientInfo) *ClientInfo {
str := string(data)
if len(str) == 0 {
return nil
}
if str[0:1] == "{" {
var request MonfloRequest
json.Unmarshal(data, &request)
if request.Uri == "/" {
if request.Headers.ApiKey != "" {
client = managementService.getClientByApiKey(request.Headers.ApiKey)
client.wsConn = wsConn
client.ClientStatus = "online"
response := WelcomeInfo{
MonfloResponse: MonfloResponse{
Event: "welcome",
},
Data: WelcomeData{
Client: client.Client,
Features: Features{
MaxUserProfiles: 1,
MaxStreams: 128,
MaxDevices: 128,
MaxP2pResolution: 2160,
MaxP2pFrames: 60,
MaxRelayResolution: 2160,
MaxRelayFrames: 60,
Hevc: true,
SessionRecording: true,
ManagementConsole: true,
ConnectAnywhere: false,
Support: false,
CommercialUse: false,
DeviceThumbnails: false,
FileTransfer: true,
},
Peers: managementService.getPeers(client.UserName),
Subscription: 3,
Timestamp: time.Now().Unix(),
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
wsConn.WriteMessage(websocket.TextMessage, []byte("{\"status\":200}"))
//如果是客户端登陆,需要通知该用户下所有的服务端
//if client.ClientType == "client" {
// streamClients := managementService.getUserAllClient(client.UserName)
// if streamClients != nil {
// for _, streamClient := range streamClients {
// if streamClient.ClientStatus == "online" && streamClient.ClientType == "server" {
// eventResponse := &ClientConnectedEvent{
// MonfloResponse: MonfloResponse{
// Event: "client_connected",
// },
// Data: client.Client,
// }
// streamClient.wsConn.WriteMessage(websocket.TextMessage, formatJson(eventResponse))
// }
// }
// }
//}
return client
} else {
//服务端匿名登录
uid, _ := uuid.NewV4()
response := WelcomeInfo{
MonfloResponse: MonfloResponse{
Event: "welcome",
},
Data: WelcomeData{
Client: "client-" + uid.String(),
Features: Features{
MaxUserProfiles: 1,
MaxStreams: 128,
MaxDevices: 128,
MaxP2pResolution: 2160,
MaxP2pFrames: 60,
MaxRelayResolution: 2160,
MaxRelayFrames: 60,
Hevc: true,
SessionRecording: true,
ManagementConsole: true,
ConnectAnywhere: false,
Support: false,
CommercialUse: false,
DeviceThumbnails: false,
FileTransfer: false,
},
Subscription: 3,
Peers: make(map[string]PeerInfo),
Timestamp: time.Now().Unix(),
},
}
client = managementService.addServer(response.Data.Client)
client.wsConn = wsConn
client.ClientType = "server"
client.ClientStatus = "online"
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
wsConn.WriteMessage(websocket.TextMessage, []byte("{\"status\":200}"))
return client
}
}
if request.Uri == "/devices/current" && request.Method == "PATCH" {
client.Metadata = request.Data.Metadata
response := PatchDeviceResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: LoginResponse{
Metadata: client.Metadata,
Id: client.Id,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
}
if request.Method == "PATCH" && strings.Contains(request.Uri, "/streams/stream-") {
streamId := request.Uri[9:]
client.Key = request.Data.Key
client.PrivateAddr = request.Data.PrivateAddr
client.PublicAddr = request.Data.PublicAddr
client.StreamStatus = request.Data.Status
response := MonfloResponse{
Status: 200,
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
//通知所有该账号的客户端更新服务端信息
streamClients := managementService.getUserAllClient(client.UserName)
if streamClients != nil {
for _, streamClient := range streamClients {
if streamClient.ClientStatus == "online" && streamClient.ClientType == "client" {
response2 := &StreamResponse{
MonfloResponse: MonfloResponse{
Event: "stream_updated",
},
Data: StreamInfo{
Id: streamId,
Key: request.Data.Key,
Status: request.Data.Status,
OwnerClient: client.Client,
PrivateAddr: request.Data.PrivateAddr,
PublicAddr: request.Data.PublicAddr,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response2))
}
}
}
}
if request.Uri == "/devices" && request.Method == "GET" {
response := DeviceResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: LoginResponse{
Metadata: client.Metadata,
Id: client.Id,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
}
if request.Uri == "/peers" && request.Method == "GET" {
peers := managementService.getPeers(client.UserName)
response := PeersResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: peers,
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
}
if request.Method == "POST" && request.Uri == "/incognitos" {
if request.Data.Stream != "" {
//登录账户模式
managementService.genId(client)
} else {
//服务端匿名模式
client.PrivateAddr = request.Data.PrivateAddr
client.PublicAddr = request.Data.PublicAddr
client.Bitrate = request.Data.Bitrate
client.Key = request.Data.Key
client.Formats = request.Data.Formats
//分配Id
managementService.genId(client)
client.Id = client.StreamId
//分配StreamId
managementService.genStream(client)
}
response := &IncognitosResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: IncognitosData{
Id: client.StreamId,
Stream: client.Stream,
Client: client.Client,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
}
if request.Method == "POST" && request.Uri == "/streams" {
client.PrivateAddr = request.Data.PrivateAddr
client.PublicAddr = request.Data.PublicAddr
client.Bitrate = request.Data.Bitrate
client.Key = request.Data.Key
client.Formats = request.Data.Formats
managementService.genStream(client)
response := &StreamResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: StreamInfo{
Id: client.Stream,
Status: "ready",
OwnerClient: client.Client,
Name: "0",
PrivateAddr: client.PrivateAddr,
PublicAddr: client.PublicAddr,
Key: client.Key,
Bitrate: client.Bitrate,
Formats: client.Formats,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
}
if request.Method == "GET" && (request.Uri == "/invitations/logins" || request.Uri == "/invitations" || request.Uri == "/shortcuts") {
response := &MonfloResponse{
Status: 200,
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
}
if request.Method == "GET" && strings.Contains(request.Uri, "/streams/stream-") {
streamCode := request.Uri[9:]
stream := managementService.getClientStream(streamCode)
if stream != nil {
response := &StreamResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: StreamInfo{
Id: stream.Stream,
Status: "ready",
OwnerClient: stream.Client,
Name: "0",
PrivateAddr: stream.PrivateAddr,
PublicAddr: stream.PublicAddr,
Key: stream.Key,
Bitrate: stream.Bitrate,
Formats: stream.Formats,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
response2 := &StreamReadResponse{
MonfloResponse: MonfloResponse{
Event: "_stream_read",
},
Data: StreamReadData{
Client: client.Client,
Format: request.Headers.Format,
Id: stream.Stream,
PrivateAddr: request.Headers.PrivateAddr,
PublicAddr: request.Headers.PublicAddr,
Type: request.Headers.Type,
},
}
stream.wsConn.WriteMessage(websocket.TextMessage, formatJson(response2))
}
}
if request.Method == "GET" && strings.Contains(request.Uri, "/incognitos/") {
stream := managementService.getClientStreamId(request.Uri[12:])
if stream != nil {
response := &StreamResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: StreamInfo{
Id: stream.Stream,
Status: "ready",
OwnerClient: stream.Client,
Name: stream.StreamId,
PrivateAddr: stream.PrivateAddr,
PublicAddr: stream.PublicAddr,
Key: stream.Key,
Bitrate: stream.Bitrate,
Formats: stream.Formats,
},
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(response))
response2 := &StreamReadResponse{
MonfloResponse: MonfloResponse{
Event: "_stream_read",
},
Data: StreamReadData{
Client: client.Client,
Format: request.Headers.Format,
Id: stream.Stream,
PrivateAddr: request.Headers.PrivateAddr,
PublicAddr: request.Headers.PublicAddr,
Type: request.Headers.Type,
},
}
stream.wsConn.WriteMessage(websocket.TextMessage, formatJson(response2))
}
}
} else if str[0:1] == "[" {
var requests []MonfloRequest
json.Unmarshal(data, &requests)
var datas []interface{}
for _, request := range requests {
if request.Uri == "/devices" && request.Method == "GET" {
device := managementService.getDevices(client.UserName)
response := DevicesResponse{
MonfloResponse: MonfloResponse{
Status: 200,
},
Data: device,
}
datas = append(datas, response)
}
if request.Uri == "/info" && request.Method == "GET" {
response := MonfloResponse{
Status: 200,
}
datas = append(datas, response)
}
}
wsConn.WriteMessage(websocket.TextMessage, formatJson(datas))
}
return nil
}
func verification(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Server", "MonfloHTTPServer")
w.Header().Set("Content-Type", "application/json")
data, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Print(err.Error())
return
}
fmt.Print("verification:" + string(data) + "\n")
var loginRequest LoginRequest
json.Unmarshal(data, &loginRequest)
if !managementService.checkExist(loginRequest.Login, loginRequest.Fingerprint) {
w.WriteHeader(404)
return
}
client := managementService.getClient(loginRequest.Login, loginRequest.Fingerprint)
client.UserName = loginRequest.Login
response := LoginResponse{
CreatedAt: client.CreatedAt,
ApiKey: client.ApiKey,
MacAddress: client.MacAddress,
Metadata: client.Metadata,
Fingerprint: client.Fingerprint,
Id: client.Id,
Client: client.Client,
Name: client.Name,
}
marshal, _ := json.Marshal(response)
w.Write(marshal)
}
func device(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Server", "MonfloHTTPServer")
w.Header().Set("Content-Type", "application/json")
data, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Print(err.Error())
return
}
fmt.Print("Add Device:" + string(data) + "\n")
var addDeviceRequest AddDeviceRequest
json.Unmarshal(data, &addDeviceRequest)
userName := r.Header.Get("Monflo-login")
managementService.addClient(userName, addDeviceRequest.Fingerprint)
managementService.addDevice(addDeviceRequest)
client := managementService.getClientByFingerprint(addDeviceRequest.Fingerprint)
response := LoginResponse{
CreatedAt: client.CreatedAt,
ApiKey: client.ApiKey,
MacAddress: client.MacAddress,
Metadata: client.Metadata,
Fingerprint: client.Fingerprint,
Id: client.Id,
Client: client.Client,
Name: client.Name,
}
marshal, _ := json.Marshal(response)
w.Write(marshal)
}

27
private.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAwcf0gqkGW79baJ39995n1YirD4x5KtnC3CsD1gftE/va3GZM
ToBsP3jdohi5TRvB/CnF6Ngv22kszDWTwLKg/K73D+5dfu8mxVyxx2kU4uzC0NR0
Z2AtumWIS05N/S53PWESBbCgPBJLcGKqLULYXf9EvEPtXebhi68C4aGnCmWOcMCL
H/VrCcwFT15eR6zsD3whv8Oqpdj5aneItpEaWd8dzBrHO69kbyfqK/GL14eLtPCl
VoY0EQ8z6hNqNTil6eseBdEuwytND5LJnfzarXCBxdnh74CEu4nxZcgEIx3hoSpL
JVtHiZfGXPZgtIech3LgcAcAmcaI6wtD94Z5pQIDAQABAoIBAAJmo9TqmzWPzWYi
bv8fNlIi+1uZ9fZd9FgeAFIqjvlsaW3JprBiTvUKXlSf0cvuyByDt/wGkbE6QF/X
WhlNHUmEMXN1FJt6AxT27Qz3dFbLcC5+M2MEggyJLYMhWT4F0VxlU3/WjGWyJFUk
I8+jwGKJwyRCAzLipXDBnluFUTiDvBbw3SpS+C6zn6wcHCfO0waDUc6Gsn9/Von/
4/AXx6tQfGYXhGYsBR/MxNQpbNol0CsAdTpi5wwZ5LUQf2Y/JlUGqQ1UwBouSZPu
k/djzrzBqsFrMqxKE4+M1TuKluawlDWPEqKgcMooegFoopYfLavKtX8lLQFDE2xo
7pIEvZECgYEA4nOc9W6iwP821p7rkUjnaaLth9jlt05t/sipN8eE8vX70ITHlNda
vQVLsAbyY3BWzyEtg+KTsDinEyzdfKuqnBRbxda/7PwZ/VfOwSLVbgkXL2No68eV
x1CzigJo7EERk+gx/zjCYlSpDVk3ycIimYkVWkIU9tmqtEXnFFdx1ocCgYEA2xEE
Bi9jh3cY2QUCewz3Dz5rMKZkkEj3lBAD7pMTy53ZTbocL1ZTnWGyacW0byiOlnzd
iny71iFAx4fdgXrhFmO/3x9duy25HJMlKyvG6U+6lD/Gr7JmX5WXGlgT61d+JfR3
mtYwaM0f2ngxWYuwvfVq/WXaB7iESNOwfOHfzXMCgYADdXKbSRJRUlSbGJhOgseO
FH/+SDDSCO+jKZt0D6cXMuyitbR6sINhSbhrOt/u5uNcjIwubIKG+YaLw26qndCg
S6tPLUWHMB6RgQrWZlrOMHNbNPCAUW8XOUNUw06o9SF4md5RoKNPby2Z15gDi+SN
Zcuesk2xq4dw83RhGijR9wKBgBiCGtUmUBhDtr/w04o4tRs7fHqA4xdRUoF6GTaD
td891aXggG67VbdxyqgSulEFVI55gb+QnOMj7T9lb96ghLYgisLHm5DpWKBdxfbC
ewp3JQSY7f2SE+n1rmYAHJpju3U7mHX2KIxRBpNGhx7hhfB6mHGpB299sS8En+YY
zxUJAoGBAKXCJqyEwDkZfov312UNQ5S7PzkuM6eTNa0Lrbo/lrn/srdMN8Iawpqs
R27AXbHEMuHjTFVIM77ju735XFn2gYdP3n5nPKWmiyd3GqSso07C3dxdTYjKPFT8
wmIf+gNAN/0r/j7G0p+pIQQ6EWOfTGmXJvdYm1ZMyUvEkKZxmO6B
-----END RSA PRIVATE KEY-----

29
request_struct.go Normal file
View File

@ -0,0 +1,29 @@
package main
type MonfloRequest struct {
Method string `json:"method,omitempty"`
Uri string `json:"uri,omitempty"`
Headers MonfloHeader `json:"headers,omitempty"`
Data MonfloRequestData `json:"data,omitempty"`
}
type MonfloHeader struct {
ApiKey string `json:"apikey,omitempty"`
PrivateAddr string `json:"private_addr,omitempty"`
PublicAddr string `json:"public_addr,omitempty"`
Type string `json:"type,omitempty"` //p2p
Format string `json:"format,omitempty"` //h264
}
type MonfloRequestData struct {
Metadata string `json:"metadata,omitempty"`
Name string `json:"name,omitempty"`
PrivateAddr string `json:"private_addr,omitempty"`
PublicAddr string `json:"public_addr,omitempty"`
Key string `json:"key,omitempty"`
Bitrate int `json:"bitrate,omitempty"`
Formats []string `json:"formats,omitempty"` //h264 h265
Stream string `json:"stream,omitempty"`
Status string `json:"status,omitempty"`
Client string `json:"client,omitempty"`
}

60
response_struct.go Normal file
View File

@ -0,0 +1,60 @@
package main
type MonfloResponse struct {
Event string `json:"event,omitempty"`
Status int `json:"status,omitempty"`
}
type DevicesResponse struct {
MonfloResponse
Data []LoginResponse `json:"data"`
}
type DeviceResponse struct {
MonfloResponse
Data LoginResponse `json:"data"`
}
type PatchDeviceResponse struct {
MonfloResponse
Data LoginResponse `json:"data"`
}
type PeersResponse struct {
MonfloResponse
Data map[string]PeerInfo `json:"data"`
}
type IncognitosResponse struct {
MonfloResponse
Data IncognitosData `json:"data"`
}
type IncognitosData struct {
Id string `json:"id"`
Stream string `json:"stream"`
Client string `json:"client"`
}
type StreamResponse struct {
MonfloResponse
Data StreamInfo `json:"data"`
}
type StreamReadResponse struct {
MonfloResponse
Data StreamReadData `json:"data"`
}
type StreamReadData struct {
Client string `json:"client"` //client-528591bf-bc07-59c5-8f24-a543bf383680
Format string `json:"format"` //h264
Id string `json:"id"` //stream-16f25ad7-be2b-5d45-a777-47d8f4c77179
PrivateAddr string `json:"private_addr"` //172.19.10.175:58169
PublicAddr string `json:"public_addr"` //58.210.98.46:44742
Type string `json:"type"` //p2p
}
type ClientConnectedEvent struct {
MonfloResponse
Data string `json:"data"`
}

57
welcome_struct.go Normal file
View File

@ -0,0 +1,57 @@
package main
type Features struct {
MaxUserProfiles int `json:"max_user_profiles"`
MaxStreams int `json:"max_streams"`
MaxDevices int `json:"max_devices"`
MaxP2pResolution int `json:"max_p2p_resolution"`
MaxP2pFrames int `json:"max_p2p_frames"`
MaxRelayResolution int `json:"max_relay_resolution"`
MaxRelayFrames int `json:"max_relay_frames"`
Hevc bool `json:"hevc"`
SessionRecording bool `json:"session_recording"`
ManagementConsole bool `json:"management_console"`
ConnectAnywhere bool `json:"connect_anywhere"`
Support bool `json:"support"`
CommercialUse bool `json:"commercial_use"`
DeviceThumbnails bool `json:"device_thumbnails"`
FileTransfer bool `json:"file_transfer"`
}
type IPInfo struct {
PrivateAddr string `json:"private_addr"`
PublicAddr string `json:"public_addr"`
}
type StreamInfo struct {
Id string `json:"id,omitempty"`
Status string `json:"status,omitempty"` //ready busy
OwnerClient string `json:"owner_client,omitempty"`
Name string `json:"name,omitempty"`
PrivateAddr string `json:"private_addr,omitempty"`
PublicAddr string `json:"public_addr,omitempty"`
Key string `json:"key,omitempty"`
Bitrate int `json:"bitrate,omitempty"`
Formats []string `json:"formats,omitempty"` //h264 h265
}
type WelcomeData struct {
Client string `json:"client,omitempty"`
Features Features `json:"features,omitempty"`
Peers map[string]PeerInfo `json:"peers,omitempty"`
Subscription int `json:"subscription,omitempty"` //1=免费 2=专业 3=旗舰
Timestamp int64 `json:"timestamp,omitempty"` //1599118479
}
type WelcomeInfo struct {
MonfloResponse
Data WelcomeData `json:"data,omitempty"`
}
type PeerInfo struct {
Name string `json:"name,omitempty"`
Status string `json:"status,omitempty"`
LastIP IPInfo `json:"last_ip,omitempty"`
Streams map[string]StreamInfo `json:"streams"`
}