bootstrap.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package bootstrap
  2. import (
  3. "context"
  4. "crypto/md5"
  5. "encoding/hex"
  6. "fmt"
  7. "net/http"
  8. "os"
  9. "time"
  10. "github.com/vladimirok5959/golang-ctrlc/ctrlc"
  11. )
  12. type Handler func(h http.Handler) http.Handler
  13. type CBServer func(s *http.Server)
  14. type Iface interface{}
  15. type BeforeAfter func(
  16. ctx context.Context,
  17. w http.ResponseWriter,
  18. r *http.Request,
  19. o *[]Iface,
  20. )
  21. type ShutdownFunc func(ctx context.Context, o *[]Iface) error
  22. type Opts struct {
  23. Handle Handler
  24. Host string
  25. Path string
  26. Cbserv CBServer
  27. Before BeforeAfter
  28. After BeforeAfter
  29. Objects *[]Iface
  30. Timeout time.Duration
  31. Shutdown ShutdownFunc
  32. }
  33. type bootstrap struct {
  34. ctx context.Context
  35. opts *Opts
  36. }
  37. func new(ctx context.Context, opts *Opts) *bootstrap {
  38. return &bootstrap{ctx: ctx, opts: opts}
  39. }
  40. func etag(str string) string {
  41. hasher := md5.New()
  42. hasher.Write([]byte(str))
  43. return hex.EncodeToString(hasher.Sum(nil))
  44. }
  45. func modified(p string, s int, v int64, w http.ResponseWriter, r *http.Request) bool {
  46. w.Header().Set("Content-Length", fmt.Sprintf("%d", s))
  47. w.Header().Set("Cache-Control", "no-cache")
  48. // Set: ETag
  49. ehash := etag(fmt.Sprintf("%s-%d-%d", p, s, v))
  50. w.Header().Set("ETag", fmt.Sprintf("%s", ehash))
  51. // Set: Last-Modified
  52. w.Header().Set(
  53. "Last-Modified",
  54. time.Unix(v, 0).In(time.FixedZone("GMT", 0)).Format("Wed, 01 Oct 2006 15:04:05 GMT"),
  55. )
  56. // Check: ETag
  57. if cc := r.Header.Get("Cache-Control"); cc != "no-cache" {
  58. if inm := r.Header.Get("If-None-Match"); inm == ehash {
  59. w.WriteHeader(http.StatusNotModified)
  60. return false
  61. }
  62. }
  63. // Check: Last-Modified
  64. if cc := r.Header.Get("Cache-Control"); cc != "no-cache" {
  65. if ims := r.Header.Get("If-Modified-Since"); ims != "" {
  66. if t, err := time.Parse("Wed, 01 Oct 2006 15:04:05 GMT", ims); err == nil {
  67. if time.Unix(v, 0).In(time.FixedZone("GMT", 0)).Unix() <= t.In(time.FixedZone("GMT", 0)).Unix() {
  68. w.WriteHeader(http.StatusNotModified)
  69. return false
  70. }
  71. }
  72. }
  73. }
  74. return true
  75. }
  76. func (this *bootstrap) handler(w http.ResponseWriter, r *http.Request) {
  77. if this.opts.Before != nil {
  78. this.opts.Before(this.ctx, w, r, this.opts.Objects)
  79. }
  80. if r.URL.Path == "/"+this.opts.Path+"/bootstrap.css" {
  81. w.Header().Set("Content-Type", "text/css")
  82. if !modified(r.URL.Path, len(rbc), rbcm, w, r) {
  83. return
  84. }
  85. w.Write(rbc)
  86. return
  87. } else if r.URL.Path == "/"+this.opts.Path+"/bootstrap.js" {
  88. w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
  89. if !modified(r.URL.Path, len(rbj), rbjm, w, r) {
  90. return
  91. }
  92. w.Write(rbj)
  93. return
  94. } else if r.URL.Path == "/"+this.opts.Path+"/jquery.js" {
  95. w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
  96. if !modified(r.URL.Path, len(rjj), rjjm, w, r) {
  97. return
  98. }
  99. w.Write(rjj)
  100. return
  101. } else if r.URL.Path == "/"+this.opts.Path+"/popper.js" {
  102. w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
  103. if !modified(r.URL.Path, len(rpj), rpjm, w, r) {
  104. return
  105. }
  106. w.Write(rpj)
  107. return
  108. }
  109. if this.opts.After != nil {
  110. this.opts.After(this.ctx, w, r, this.opts.Objects)
  111. }
  112. }
  113. func (this *bootstrap) Shutdown(ctx context.Context) error {
  114. if this.opts.Shutdown != nil {
  115. return this.opts.Shutdown(ctx, this.opts.Objects)
  116. }
  117. return nil
  118. }
  119. func Start(opts *Opts) {
  120. if opts == nil {
  121. fmt.Println("Start: options is not defined")
  122. os.Exit(1)
  123. }
  124. ctrlc.App(
  125. opts.Timeout,
  126. func(ctx context.Context, shutdown context.CancelFunc) *[]ctrlc.Iface {
  127. bt := new(ctx, opts)
  128. mux := http.NewServeMux()
  129. mux.HandleFunc("/", bt.handler)
  130. var srv *http.Server
  131. if opts.Handle == nil {
  132. srv = &http.Server{
  133. Addr: opts.Host,
  134. Handler: mux,
  135. }
  136. } else {
  137. srv = &http.Server{
  138. Addr: opts.Host,
  139. Handler: opts.Handle(mux),
  140. }
  141. }
  142. if opts.Cbserv != nil {
  143. opts.Cbserv(srv)
  144. }
  145. go func() {
  146. fmt.Printf("Starting server at http://%s/\n", opts.Host)
  147. if err := srv.ListenAndServe(); err != nil {
  148. if err != http.ErrServerClosed {
  149. fmt.Printf("Web server startup error: %s\n", err.Error())
  150. shutdown()
  151. return
  152. }
  153. }
  154. }()
  155. return &[]ctrlc.Iface{bt, srv}
  156. },
  157. )
  158. }