kserv_http.go 5.8 KB

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