ajoute le proxy console Go et le relais WebSocket vers nova-serialproxy
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
30e14b1c54
commit
8742bde807
30 changed files with 1103 additions and 6 deletions
156
proxy-console/hub.go
Normal file
156
proxy-console/hub.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Un Hub représente une seule connexion amont vers nova-serialproxy, pour une
|
||||
// seule VM (identifiée par son instance_id). La console série n'accepte
|
||||
// qu'une connexion à la fois : le Hub est le seul à parler à nova-serialproxy,
|
||||
// et duplique ce qu'il reçoit vers tous les navigateurs abonnés (l'élève qui
|
||||
// écrit, le prof qui regarde en lecture seule).
|
||||
type Hub struct {
|
||||
instanceID string
|
||||
upstream *websocket.Conn
|
||||
|
||||
mu sync.Mutex
|
||||
subscribers map[*Client]bool
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
conn *websocket.Conn
|
||||
role string // "ecriture" ou "lecture"
|
||||
hub *Hub
|
||||
}
|
||||
|
||||
var (
|
||||
hubsMu sync.Mutex
|
||||
hubs = map[string]*Hub{}
|
||||
)
|
||||
|
||||
var upstreamDialer = websocket.Dialer{
|
||||
HandshakeTimeout: 10 * time.Second,
|
||||
Subprotocols: []string{"binary"},
|
||||
}
|
||||
|
||||
// obtenirHub renvoie le Hub existant pour cette instance, ou en ouvre un
|
||||
// nouveau (une seule connexion amont) si c'est le premier abonné.
|
||||
func obtenirHub(instanceID, urlAmont string) (*Hub, error) {
|
||||
hubsMu.Lock()
|
||||
defer hubsMu.Unlock()
|
||||
|
||||
if hub, ok := hubs[instanceID]; ok {
|
||||
return hub, nil
|
||||
}
|
||||
|
||||
upstream, _, err := upstreamDialer.Dial(urlAmont, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hub := &Hub{
|
||||
instanceID: instanceID,
|
||||
upstream: upstream,
|
||||
subscribers: map[*Client]bool{},
|
||||
}
|
||||
hubs[instanceID] = hub
|
||||
go hub.pompeAmont()
|
||||
|
||||
log.Printf("console %s : connexion amont ouverte", instanceID)
|
||||
|
||||
return hub, nil
|
||||
}
|
||||
|
||||
// pompeAmont lit en continu ce que renvoie nova-serialproxy et le recopie
|
||||
// vers tous les navigateurs abonnés à cette VM.
|
||||
func (h *Hub) pompeAmont() {
|
||||
defer h.fermer()
|
||||
|
||||
for {
|
||||
messageType, donnees, err := h.upstream.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
for client := range h.subscribers {
|
||||
_ = client.conn.WriteMessage(messageType, donnees)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) ajouter(c *Client) {
|
||||
h.mu.Lock()
|
||||
h.subscribers[c] = true
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
// retirer enlève l'abonné ; si c'était le dernier, la connexion amont est
|
||||
// fermée (plus personne ne regarde cette VM, pas de raison de la garder).
|
||||
func (h *Hub) retirer(c *Client) {
|
||||
h.mu.Lock()
|
||||
delete(h.subscribers, c)
|
||||
vide := len(h.subscribers) == 0
|
||||
h.mu.Unlock()
|
||||
|
||||
if vide {
|
||||
h.fermer()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) fermer() {
|
||||
hubsMu.Lock()
|
||||
if hubs[h.instanceID] == h {
|
||||
delete(hubs, h.instanceID)
|
||||
}
|
||||
hubsMu.Unlock()
|
||||
|
||||
_ = h.upstream.Close()
|
||||
log.Printf("console %s : connexion amont fermée", h.instanceID)
|
||||
}
|
||||
|
||||
// pompeClient lit ce que tape le navigateur et le relaie vers nova-serialproxy
|
||||
// — uniquement si ce client a le rôle "ecriture". Un abonné en lecture seule
|
||||
// ne peut rien envoyer : ses frappes sont simplement ignorées.
|
||||
func (c *Client) pompeClient() {
|
||||
defer c.hub.retirer(c)
|
||||
defer c.conn.Close()
|
||||
|
||||
for {
|
||||
messageType, donnees, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.role != "ecriture" {
|
||||
continue
|
||||
}
|
||||
|
||||
c.hub.mu.Lock()
|
||||
err = c.hub.upstream.WriteMessage(messageType, donnees)
|
||||
c.hub.mu.Unlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// N'accepte que les connexions dont l'en-tête Origin correspond au site qui
|
||||
// sert la page (reflété via Nginx) : empêche un autre site de faire ouvrir
|
||||
// une console depuis le navigateur d'un élève à son insu.
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
origine := r.Header.Get("Origin")
|
||||
if origine == "" {
|
||||
return true // outils en ligne de commande, pas de navigateur
|
||||
}
|
||||
return strings.HasSuffix(origine, "://"+r.Host)
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue