Files
Bridge-and-Join-s/internal/lkgateway/handbook_embed.go
T

147 lines
5.0 KiB
Go

package lkgateway
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
// Справочник участников M2M (открытые данные НРД). Источники, из которых берётся самый свежий:
// - полученный от НРД этим bj (M2MTH через шлюз НРД) — handbookFile;
// - копия с сервера лицензий (её присылает установка со шлюзом НРД, помеченная источником) —
// handbookCloudFile, забирается раз в сутки;
// - снимок, встроенный в bj (чистая установка без шлюза и без связи).
//
//go:embed handbook/m2m-handbook.json
var handbookSnapshot []byte
const handbookCloudFile = "/var/lib/bj/.bj/m2m-handbook-cloud.json"
func handbookAt(b []byte) time.Time {
var h struct {
At time.Time `json:"at"`
}
_ = json.Unmarshal(b, &h)
return h.At
}
// readHandbook — самый свежий из доступных справочников.
func readHandbook() ([]byte, error) {
best := handbookSnapshot
for _, p := range []string{handbookFile, handbookCloudFile} {
if b, err := os.ReadFile(p); err == nil && len(b) > 0 && handbookAt(b).After(handbookAt(best)) {
best = b
}
}
return best, nil
}
func handbookURL(rc *RuntimeConfig) string {
base := strings.TrimSpace(rc.Snapshot().Update.BaseURL)
if base == "" {
base = DefaultUpdateURL
}
return strings.TrimRight(base, "/") + "/v1/handbook"
}
// pushHandbookAsync — отправить полученный от НРД справочник на сервер лицензий. Сервер примет его,
// только если лицензия отмечена как источник справочника; у остальных — тихий отказ.
func pushHandbookAsync(rc *RuntimeConfig, raw []byte) {
if rc == nil || strings.TrimSpace(rc.Snapshot().License.Key) == "" {
return
}
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPut, handbookURL(rc), bytes.NewReader(raw))
if err != nil {
return
}
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(rc.Snapshot().License.Key))
req.Header.Set("Content-Type", "application/json")
resp, err := licenseHTTP.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNoContent {
log.Printf("lk-gateway: справочник участников отправлен на сервер лицензий")
}
}()
}
// fetchHandbook — забрать справочник с сервера лицензий, если он свежее уже имеющихся.
func fetchHandbook(ctx context.Context, rc *RuntimeConfig) error {
key := strings.TrimSpace(rc.Snapshot().License.Key)
if key == "" {
return nil
}
if fi, err := os.Stat(handbookFile); err == nil && fi.Size() > 0 {
return nil // справочник уже приходит от НРД через шлюз — к серверу лицензий не обращаемся
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, handbookURL(rc), nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := licenseHTTP.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP %d", resp.StatusCode)
}
b, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
if err != nil {
return err
}
var doc struct {
At time.Time `json:"at"`
Participants []json.RawMessage `json:"participants"`
}
if json.Unmarshal(b, &doc) != nil || len(doc.Participants) == 0 || doc.At.IsZero() {
return fmt.Errorf("не справочник")
}
cur, _ := readHandbook()
if !doc.At.After(handbookAt(cur)) {
return nil
}
if err := os.WriteFile(handbookCloudFile, b, 0o600); err != nil {
return err
}
log.Printf("lk-gateway: справочник участников обновлён с сервера лицензий (%d участников, от %s)", len(doc.Participants), doc.At.Format("02.01.2006 15:04"))
return nil
}
// handbookLoop — пока своего справочника от НРД нет (новая установка без шлюза), раз в сутки
// забирает копию с сервера лицензий; как только справочник пришёл от НРД — больше не обращается.
func handbookLoop(ctx context.Context, rc *RuntimeConfig) {
t := time.NewTimer(2 * time.Minute)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
}
cctx, cancel := context.WithTimeout(ctx, time.Minute)
if err := fetchHandbook(cctx, rc); err != nil {
log.Printf("lk-gateway: справочник с сервера лицензий: %v", err)
}
cancel()
t.Reset(24 * time.Hour)
}
}