kserv_http.go 6.0 KB

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