kserv_http.go 5.9 KB

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