// package kserv_http -- встроенный HTTP-сервер. package kserv_http import ( "embed" "fmt" "io/fs" "net/http" "os" "strings" "time" "github.com/gofiber/contrib/v3/monitor" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/middleware/compress" "github.com/gofiber/fiber/v3/middleware/static" "gitp78su.ipnodns.ru/svi/kern/v4/d0" "gitp78su.ipnodns.ru/svi/kern/v4/d1" "gitp78su.ipnodns.ru/svi/kern/v4/d2/kern_ctx" "gitp78su.ipnodns.ru/svi/kern/v4/d2/kern_ent/stream_name" "gitp78su.ipnodns.ru/svi/kern/v4/d2/kspec" ) var ( msg0 = d0.Txt("kernel_server_http") streamName = stream_name.NewAStreamName(msg0) // Контрольная строка для ожидателя потока ) // kServHttp -- встроенный HTTP-сервер. type kServHttp struct { owner *d0.LOwner kCtx *kern_ctx.KernCtx lCtx *d1.LocalCtx log *d1.LogBuf strUrl *d0.LTxt // URL, на котором слушает HTTP-сервер fiberApp *fiber.App isWork *d0.MSafeBool isEnd *d0.MSafeBool } //go:embed static/* var embedDirStatic embed.FS var ( kernServHttp *kServHttp block = d0.MutSafeMutex() ) func getParams(owner *d0.LOwner) (*d0.LTxt, *d1.LogBuf, *kern_ctx.KernCtx) { param := &d1.LogBufParam{ IsTerm_: d0.MutSafeBool(owner, true), Prefix_: d0.LetTxt("kServHttp"), } log := d1.NewLogBuf(param) log.Debug("GetKernelServHttp(): first run") strLocalUrl := os.Getenv("LOCAL_HTTP_URL") d0.If(strLocalUrl == "").Hassert("getParams(): env LOCAL_HTTP_URL not set") kCtx := kern_ctx.GetKernCtx() return d0.LetTxt(strLocalUrl), log, kCtx } // GetKernelServHttp -- возвращает встроенный HTTP-сервер. func GetKernelServHttp() kspec.IKernelServerHttp { block.Lock() defer block.Unlock() if kernServHttp != nil { kernServHttp.log.Debug("GetKernelServHttp()") return kernServHttp } owner := d0.LetOwner(1, "isol_kern_serv_http") strLocalUrl, log, kCtx := getParams(owner) optMonolit := kCtx.Get(d0.LetTxt("monolitName")) optMonolit.Hassert("GetKernelServHttp(): not have monolit name from kCtx") strMonolit := optMonolit.Some().Val().(*d0.LTime) confFiber := fiber.Config{ ServerHeader: strMonolit.String(), UnescapePath: true, ReadTimeout: time.Second * 15, WriteTimeout: time.Second * 15, AppName: strMonolit.String(), } sf := &kServHttp{ owner: owner, kCtx: kCtx, log: log, lCtx: d1.NewLocalCtx(owner, kCtx.SelfCtx()), strUrl: strLocalUrl, fiberApp: fiber.New(confFiber), isWork: d0.MutSafeBool(owner, false), isEnd: d0.MutSafeBool(owner, false), } sf.fiberApp.Use(compress.New(compress.Config{ Level: compress.LevelBestCompression, // 2 })) sf.fiberApp.Get("/static", static.New("/static", static.Config{ FS: fs.FS(embedDirStatic), Browse: true, MaxAge: 3600 * 24, })) // Или расширьте конфигурацию для индивидуальной настройки // Назначьте промежуточное программное обеспечение для /metrics // и измените заголовок на `Страница метрик MyService` sf.fiberApp.Get("/metrics", monitor.New(monitor.Config{ Title: fmt.Sprintf("Страница метрик %q", strMonolit), })) sf.kCtx.Wg().Add(streamName) kCtx.Set(d0.LetTxt("fiberApp"), sf.fiberApp, "GetKernelServHttp() internal fiber app") kernServHttp = sf kCtx.Set(d0.LetTxt("kServHttp"), kernServHttp, "kServHttp") return kernServHttp } // IsWork -- возвращает признак работы. func (sf *kServHttp) IsWork() d0.Bool { return sf.isWork.IsOk() } // Log -- возвращает локальный лог. func (sf *kServHttp) Log() *d1.LogBuf { return sf.log } // Fiber -- возвращает объект веб-приложения fiber. func (sf *kServHttp) Fiber() *fiber.App { return sf.fiberApp } // Run -- запускает сервер в работу (не блокирующий вызов). func (sf *kServHttp) Run() { block.Lock() defer block.Unlock() if sf.isEnd.IsOk() { return } if sf.isWork.IsOk() { return } sf.log.Debug("Run(): url='%v'", sf.strUrl) lstPort := strings.Split(sf.strUrl.String(), ":") strPort := lstPort[len(lstPort)-1] strPort = strings.ReplaceAll(strPort, "/", "") strPort = strings.ReplaceAll(strPort, `"`, "") chErr := make(chan *d0.LErr, 2) fnListen := func() { defer close(chErr) err := sf.fiberApp.Listen(":" + strPort) chErr <- d0.LetErr("kServHttp.Run().fnListen(): in listen server on port=%q, err=\n\t%v", strPort, err) } go fnListen() go sf.fnChErr(chErr) fnCheckServer := func() error { client := &http.Client{Timeout: 5 * time.Millisecond} url := d0.LetTxt(sf.strUrl.Get() + "monitor") sf.log.Debug("url=%v", url.Get()) var ( resp *http.Response err error ) if resp, err = client.Get(url.String()); err == nil { _ = resp.Body.Close() } return err } for { time.Sleep(time.Millisecond * 10) err := fnCheckServer() if err == nil { break } } sf.isWork.Set() go sf.close() } // В отдельном потоке ждёт закрытия канала. func (sf *kServHttp) fnChErr(chErr <-chan *d0.LErr) { err := <-chErr if err.Get() != "" { err := d0.LetErr("kServHttp.fnChErr(): in listen, err=\n\t%w", err) sf.log.Err(err) sf.kCtx.Cancel() } } // Ожидает окончания работы. func (sf *kServHttp) close() { sf.kCtx.Wait() if sf.isWork.IsNot() { return } sf.isWork.Reset() sf.isEnd.Set() err := sf.fiberApp.Server().Shutdown() d0.If(err != nil).Assert("kServHttp.close(): in close server, err=\n\t%v", err) sf.kCtx.Wg().Done(streamName) sf.log.Debug("close(): end") }