kserv_http.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // package kserv_http -- встроенный HTTP-сервер.
  2. package kserv_http
  3. import (
  4. "embed"
  5. "fmt"
  6. "net/http"
  7. "os"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/gofiber/fiber/v2/middleware/compress"
  12. "github.com/gofiber/fiber/v2/middleware/filesystem"
  13. "github.com/gofiber/fiber/v2/middleware/monitor"
  14. "github.com/gofiber/fiber/v3"
  15. "gitp78su.ipnodns.ru/svi/kern/v4/lev0/defs"
  16. "gitp78su.ipnodns.ru/svi/kern/v4/lev0/helpers"
  17. mKs "gitp78su.ipnodns.ru/svi/kern/v4/lev0/kspec"
  18. mL1 "gitp78su.ipnodns.ru/svi/kern/v4/lev1"
  19. "gitp78su.ipnodns.ru/svi/kern/v4/lev2/kctx"
  20. )
  21. var (
  22. streamName = defs.NewStreamName("kernel_server_http") // Контрольная строка для ожидателя потока
  23. )
  24. // kServHttp -- встроенный HTTP-сервер.
  25. type kServHttp struct {
  26. kCtx mKs.IKernelCtx
  27. lCtx mKs.ILocalCtx
  28. log mKs.ILogBuf
  29. strUrl string // URL, на котором слушает HTTP-сервер
  30. fiberApp *fiber.App
  31. isWork mKs.ISafeBool
  32. isEnd mKs.ISafeBool
  33. }
  34. //go:embed static/*
  35. var embedDirStatic embed.FS
  36. var (
  37. kernServHttp *kServHttp
  38. block sync.Mutex
  39. assert = helpers.Assert
  40. hassert = helpers.Hassert
  41. )
  42. func getParams() (string, mKs.ILogBuf, mKs.IKernelCtx) {
  43. log := mL1.NewLogBuf(mL1.OptIsTerm(true), mL1.OptPrefix("kServHttp"))
  44. log.Debug("GetKernelServHttp(): first run")
  45. strLocalUrl := os.Getenv("LOCAL_HTTP_URL")
  46. hassert(strLocalUrl != "", "getParams(): env LOCAL_HTTP_URL not set")
  47. kCtx := kctx.GetKernelCtx()
  48. return strLocalUrl, log, kCtx
  49. }
  50. // GetKernelServHttp -- возвращает встроенный HTTP-сервер.
  51. func GetKernelServHttp() mKs.IKernelServerHttp {
  52. block.Lock()
  53. defer block.Unlock()
  54. if kernServHttp != nil {
  55. kernServHttp.log.Debug("GetKernelServHttp()")
  56. return kernServHttp
  57. }
  58. strLocalUrl, log, kCtx := getParams()
  59. optMonolit := kCtx.Get("monolitName")
  60. optMonolit.Hassert("GetKernelServHttp(): not have monolit name from kCtx")
  61. strMonolit := optMonolit.Some().Val().(string)
  62. confFiber := fiber.Config{
  63. ServerHeader: strMonolit,
  64. UnescapePath: true,
  65. ReadTimeout: time.Second * 15,
  66. WriteTimeout: time.Second * 15,
  67. AppName: strMonolit,
  68. }
  69. sf := &kServHttp{
  70. kCtx: kCtx,
  71. log: log,
  72. lCtx: mL1.NewLocalCtx(kCtx.Ctx()),
  73. strUrl: strLocalUrl,
  74. fiberApp: fiber.New(confFiber),
  75. isWork: mL1.NewSafeBool(),
  76. isEnd: mL1.NewSafeBool(),
  77. }
  78. sf.fiberApp.Use(compress.New(compress.Config{
  79. Level: compress.LevelBestCompression, // 2
  80. }))
  81. sf.fiberApp.Use("/static", filesystem.New(filesystem.Config{
  82. Root: http.FS(embedDirStatic),
  83. PathPrefix: "static",
  84. Browse: true,
  85. MaxAge: 3600 * 24,
  86. }))
  87. sf.fiberApp.Get("/monitor", monitor.New(monitor.Config{Title: strMonolit}))
  88. sf.kCtx.Wg().Add(streamName)
  89. kCtx.Set("fiberApp", sf.fiberApp, "GetKernelServHttp() internal fiber app")
  90. kernServHttp = sf
  91. kCtx.Set("kServHttp", kernServHttp, "kServHttp")
  92. return kernServHttp
  93. }
  94. // IsWork -- возвращает признак работы.
  95. func (sf *kServHttp) IsWork() bool {
  96. return sf.isWork.Get()
  97. }
  98. // Log -- возвращает локальный лог.
  99. func (sf *kServHttp) Log() mKs.ILogBuf {
  100. return sf.log
  101. }
  102. // Fiber -- возвращает объект веб-приложения fiber.
  103. func (sf *kServHttp) Fiber() *fiber.App {
  104. return sf.fiberApp
  105. }
  106. // Run -- запускает сервер в работу (не блокирующий вызов).
  107. func (sf *kServHttp) Run() {
  108. block.Lock()
  109. defer block.Unlock()
  110. if sf.isEnd.Get() {
  111. return
  112. }
  113. if sf.isWork.Get() {
  114. return
  115. }
  116. sf.log.Debug("Run(): url='%v'", sf.strUrl)
  117. lstPort := strings.Split(sf.strUrl, ":")
  118. strPort := lstPort[len(lstPort)-1]
  119. strPort = strings.ReplaceAll(strPort, "/", "")
  120. strPort = strings.ReplaceAll(strPort, `"`, "")
  121. chErr := make(chan string, 2)
  122. fnListen := func() {
  123. defer close(chErr)
  124. err := sf.fiberApp.Listen(":" + strPort)
  125. chErr <- fmt.Sprint(err)
  126. }
  127. go fnListen()
  128. go sf.fnChErr(chErr)
  129. fnCheckServer := func() error {
  130. client := &http.Client{Timeout: 5 * time.Millisecond}
  131. url := sf.strUrl + "monitor"
  132. sf.log.Debug("url=%v", url)
  133. var (
  134. resp *http.Response
  135. err error
  136. )
  137. if resp, err = client.Get(url); err == nil {
  138. _ = resp.Body.Close()
  139. }
  140. return err
  141. }
  142. for {
  143. time.Sleep(time.Millisecond * 10)
  144. err := fnCheckServer()
  145. if err == nil {
  146. break
  147. }
  148. }
  149. sf.isWork.Set()
  150. go sf.close()
  151. }
  152. // В отдельном потоке ждёт закрытия канала.
  153. func (sf *kServHttp) fnChErr(chErr <-chan string) {
  154. strErr := <-chErr
  155. if strErr != "<nil>" {
  156. err := fmt.Errorf("kServHttp.fnChErr(): in listen, err=\n\t%v", strErr)
  157. sf.log.Err("Run(): err=\n\t%v", err.Error())
  158. sf.kCtx.Cancel()
  159. }
  160. }
  161. // Ожидает окончания работы.
  162. func (sf *kServHttp) close() {
  163. sf.kCtx.Wait()
  164. if !sf.isWork.Get() {
  165. return
  166. }
  167. sf.isWork.Reset()
  168. sf.isEnd.Set()
  169. err := sf.fiberApp.Server().Shutdown()
  170. assert(err == nil, "kServHttp.close(): in close server, err=\n\t%v", err)
  171. sf.kCtx.Wg().Done(streamName)
  172. sf.log.Debug("close(): end")
  173. }