ColorPing/http.go
2023-03-06 20:31:23 +02:00

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()
}
}