| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- package server
- import (
- "fmt"
- "sync"
- "time"
- )
- // ClientInfo captures metadata about a connected frpc instance.
- type ClientInfo struct {
- Key string
- User string
- ClientID string
- RunID string
- Hostname string
- IP string
- FirstConnectedAt time.Time
- LastConnectedAt time.Time
- DisconnectedAt time.Time
- Online bool
- }
- // ClientRegistry keeps track of active clients keyed by "{user}.{clientID}" (or runID if clientID is empty).
- // Entries without an explicit clientID are removed on disconnect to avoid stale offline records.
- type ClientRegistry struct {
- mu sync.RWMutex
- clients map[string]*ClientInfo
- runIndex map[string]string
- }
- func NewClientRegistry() *ClientRegistry {
- return &ClientRegistry{
- clients: make(map[string]*ClientInfo),
- runIndex: make(map[string]string),
- }
- }
- // Register stores/updates metadata for a client and returns the registry key plus whether it conflicts with an online client.
- func (cr *ClientRegistry) Register(user, clientID, runID, hostname, remoteAddr string) (key string, conflict bool) {
- if runID == "" {
- return "", false
- }
- effectiveID := clientID
- if effectiveID == "" {
- effectiveID = runID
- }
- key = cr.composeClientKey(user, effectiveID)
- enforceUnique := clientID != ""
- now := time.Now()
- cr.mu.Lock()
- defer cr.mu.Unlock()
- info, exists := cr.clients[key]
- if enforceUnique && exists && info.Online && info.RunID != "" && info.RunID != runID {
- return key, true
- }
- if !exists {
- info = &ClientInfo{
- Key: key,
- User: user,
- ClientID: clientID,
- FirstConnectedAt: now,
- }
- cr.clients[key] = info
- } else if info.RunID != "" {
- delete(cr.runIndex, info.RunID)
- }
- info.RunID = runID
- info.Hostname = hostname
- info.IP = remoteAddr
- if info.FirstConnectedAt.IsZero() {
- info.FirstConnectedAt = now
- }
- info.LastConnectedAt = now
- info.DisconnectedAt = time.Time{}
- info.Online = true
- cr.runIndex[runID] = key
- return key, false
- }
- // MarkOfflineByRunID marks the client as offline when the corresponding control disconnects.
- func (cr *ClientRegistry) MarkOfflineByRunID(runID string) {
- cr.mu.Lock()
- defer cr.mu.Unlock()
- key, ok := cr.runIndex[runID]
- if !ok {
- return
- }
- if info, ok := cr.clients[key]; ok && info.RunID == runID {
- if info.ClientID == "" {
- delete(cr.clients, key)
- } else {
- info.RunID = ""
- info.Online = false
- now := time.Now()
- info.DisconnectedAt = now
- }
- }
- delete(cr.runIndex, runID)
- }
- // List returns a snapshot of all known clients.
- func (cr *ClientRegistry) List() []ClientInfo {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
- result := make([]ClientInfo, 0, len(cr.clients))
- for _, info := range cr.clients {
- result = append(result, *info)
- }
- return result
- }
- // GetByKey retrieves a client by its composite key ({user}.{clientID} or runID fallback).
- func (cr *ClientRegistry) GetByKey(key string) (ClientInfo, bool) {
- cr.mu.RLock()
- defer cr.mu.RUnlock()
- info, ok := cr.clients[key]
- if !ok {
- return ClientInfo{}, false
- }
- return *info, true
- }
- func (cr *ClientRegistry) composeClientKey(user, id string) string {
- switch {
- case user == "":
- return id
- case id == "":
- return user
- default:
- return fmt.Sprintf("%s.%s", user, id)
- }
- }
|