mirror of
https://github.com/Kioubit/ColorPing
synced 2024-11-22 07:40:42 +08:00
221 lines
3.9 KiB
Go
221 lines
3.9 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"embed"
|
||
|
"html/template"
|
||
|
"log"
|
||
|
"math"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
//go:embed template.html
|
||
|
var embedFS embed.FS
|
||
|
var htmlTemplate *template.Template
|
||
|
|
||
|
type clientState int
|
||
|
|
||
|
const (
|
||
|
INITIAL = 0
|
||
|
ACTIVE = iota
|
||
|
)
|
||
|
|
||
|
type client struct {
|
||
|
channel chan string
|
||
|
state clientState
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
clientCounter uint32 = 0
|
||
|
clientCounterMutex sync.Mutex
|
||
|
|
||
|
clients = make(map[uint32]*client)
|
||
|
clientMutex sync.RWMutex
|
||
|
)
|
||
|
|
||
|
func getClientID() uint32 {
|
||
|
clientCounterMutex.Lock()
|
||
|
defer clientCounterMutex.Unlock()
|
||
|
clientCounter++
|
||
|
if clientCounter == math.MaxUint32 {
|
||
|
clientCounter = 0
|
||
|
clearClients()
|
||
|
}
|
||
|
return clientCounter
|
||
|
}
|
||
|
|
||
|
func clearClients() {
|
||
|
clientMutex.Lock()
|
||
|
defer clientMutex.Unlock()
|
||
|
clients = make(map[uint32]*client)
|
||
|
}
|
||
|
|
||
|
func httpServer() {
|
||
|
var err error
|
||
|
|
||
|
htmlTemplate = template.Must(template.ParseFS(embedFS, "template.html"))
|
||
|
http.HandleFunc("/stream", stream)
|
||
|
http.HandleFunc("/", serveRoot)
|
||
|
err = http.ListenAndServe("0.0.0.0:9090", nil)
|
||
|
if err != nil {
|
||
|
log.Fatalln(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getInterfaceBaseIP() string {
|
||
|
iFace, err := net.InterfaceByName(interfaceName)
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
addresses, err := iFace.Addrs()
|
||
|
if err != nil {
|
||
|
return ""
|
||
|
}
|
||
|
gua := ""
|
||
|
ula := ""
|
||
|
for _, v := range addresses {
|
||
|
addr := v.String()
|
||
|
if !strings.Contains(addr, ":") {
|
||
|
continue
|
||
|
}
|
||
|
_, anet, err := net.ParseCIDR(addr)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
if anet.IP.IsLinkLocalUnicast() {
|
||
|
continue
|
||
|
}
|
||
|
if anet.IP.IsGlobalUnicast() {
|
||
|
gua = strings.Split(anet.String(), "/")[0]
|
||
|
}
|
||
|
if anet.IP.IsPrivate() {
|
||
|
ula = strings.Split(anet.String(), "/")[0]
|
||
|
}
|
||
|
}
|
||
|
if gua != "" {
|
||
|
return gua
|
||
|
} else {
|
||
|
return ula
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func serveRoot(w http.ResponseWriter, r *http.Request) {
|
||
|
if r.RequestURI != "/" {
|
||
|
w.WriteHeader(404)
|
||
|
_, _ = w.Write([]byte("404 not found"))
|
||
|
return
|
||
|
}
|
||
|
type pageData struct {
|
||
|
BaseIP string
|
||
|
CanvasWidth int
|
||
|
CanvasHeight int
|
||
|
}
|
||
|
baseIP := getInterfaceBaseIP()
|
||
|
if len(baseIP) == 21 {
|
||
|
baseIP = strings.TrimSuffix(baseIP, ":")
|
||
|
}
|
||
|
err := htmlTemplate.Execute(w, pageData{
|
||
|
BaseIP: baseIP,
|
||
|
CanvasHeight: 512,
|
||
|
CanvasWidth: 512,
|
||
|
})
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func streamServer() {
|
||
|
for {
|
||
|
clientMutex.RLock()
|
||
|
if len(clients) == 0 {
|
||
|
for {
|
||
|
if len(clients) == 0 {
|
||
|
clientMutex.RUnlock()
|
||
|
time.Sleep(1 * time.Second)
|
||
|
clientMutex.RLock()
|
||
|
} else {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
requiresInitial := false
|
||
|
requiresUpdate := false
|
||
|
for _, v := range clients {
|
||
|
if v.state == INITIAL {
|
||
|
requiresInitial = true
|
||
|
} else {
|
||
|
requiresUpdate = true
|
||
|
}
|
||
|
if requiresInitial && requiresUpdate {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dataInitial, dataUpdate := getPicture(requiresInitial, requiresUpdate)
|
||
|
|
||
|
for _, v := range clients {
|
||
|
if v.state == INITIAL {
|
||
|
v.state = ACTIVE
|
||
|
select {
|
||
|
case v.channel <- dataInitial:
|
||
|
default:
|
||
|
continue
|
||
|
}
|
||
|
} else {
|
||
|
if dataUpdate != "0" {
|
||
|
select {
|
||
|
case v.channel <- dataUpdate:
|
||
|
default:
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
time.Sleep(400 * time.Millisecond)
|
||
|
}
|
||
|
clientMutex.RUnlock()
|
||
|
time.Sleep(10 * time.Millisecond)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func stream(w http.ResponseWriter, r *http.Request) {
|
||
|
flusher, ok := w.(http.Flusher)
|
||
|
if !ok {
|
||
|
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||
|
return
|
||
|
}
|
||
|
w.Header().Set("Content-Type", "text/event-stream")
|
||
|
w.Header().Set("Cache-Control", "no-cache")
|
||
|
w.Header().Set("Connection", "keep-alive")
|
||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||
|
messageChan := make(chan string, 40)
|
||
|
id := getClientID()
|
||
|
newClient := client{
|
||
|
channel: messageChan,
|
||
|
state: INITIAL,
|
||
|
}
|
||
|
clientMutex.Lock()
|
||
|
clients[id] = &newClient
|
||
|
clientMutex.Unlock()
|
||
|
go func() {
|
||
|
// Listen for connection close
|
||
|
<-r.Context().Done()
|
||
|
clientMutex.Lock()
|
||
|
close(messageChan)
|
||
|
delete(clients, id)
|
||
|
clientMutex.Unlock()
|
||
|
}()
|
||
|
|
||
|
for {
|
||
|
data := <-messageChan
|
||
|
if data == "" {
|
||
|
return
|
||
|
}
|
||
|
_, _ = w.Write([]byte(data))
|
||
|
flusher.Flush()
|
||
|
}
|
||
|
}
|