cli.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. package iris
  2. // +------------------------------------------------------------+
  3. // | Bridge code between iris-cli and iris web application |
  4. // | https://github.com/kataras/iris-cli |
  5. // +------------------------------------------------------------+
  6. import (
  7. "bytes"
  8. "fmt"
  9. "net/http"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "github.com/kataras/iris/context"
  14. "github.com/kataras/iris/core/router"
  15. "gopkg.in/yaml.v3"
  16. )
  17. // injectLiveReload tries to check if this application
  18. // runs under https://github.com/kataras/iris-cli and if so
  19. // then it checks if the livereload is enabled and then injects
  20. // the watch listener (js script) on every HTML response.
  21. // It has a slight performance cost but
  22. // this (iris-cli with watch and livereload enabled)
  23. // is meant to be used only in development mode.
  24. // It does a full reload at the moment and if the port changed
  25. // at runtime it will fire 404 instead of redirecting to the correct port (that's a TODO).
  26. //
  27. // tryInjectLiveReload runs right before Build -> BuildRouter.
  28. func injectLiveReload(contextPool *context.Pool, router *router.Router) (bool, error) {
  29. conf := struct {
  30. Running bool `yaml:"Running,omitempty"`
  31. LiveReload struct {
  32. Disable bool `yaml:"Disable"`
  33. Port int `yaml:"Port"`
  34. } `yaml:"LiveReload"`
  35. }{}
  36. // defaults to disabled here.
  37. conf.LiveReload.Disable = true
  38. wd, err := os.Getwd()
  39. if err != nil {
  40. return false, err
  41. }
  42. for _, path := range []string{".iris.yml" /*, "../.iris.yml", "../../.iris.yml" */} {
  43. path = filepath.Join(wd, path)
  44. if _, err := os.Stat(path); err == nil {
  45. inFile, err := os.OpenFile(path, os.O_RDONLY, 0644)
  46. if err != nil {
  47. return false, err
  48. }
  49. dec := yaml.NewDecoder(inFile)
  50. err = dec.Decode(&conf)
  51. inFile.Close()
  52. if err != nil {
  53. return false, err
  54. }
  55. break
  56. }
  57. }
  58. if !conf.Running || conf.LiveReload.Disable {
  59. return false, nil
  60. }
  61. scriptReloadJS := []byte(fmt.Sprintf(`<script>(function () {
  62. const scheme = document.location.protocol == "https:" ? "wss" : "ws";
  63. const endpoint = scheme + "://" + document.location.hostname + ":%d/livereload";
  64. w = new WebSocket(endpoint);
  65. w.onopen = function () {
  66. console.info("LiveReload: initialization");
  67. };
  68. w.onclose = function () {
  69. console.info("LiveReload: terminated");
  70. };
  71. w.onmessage = function (message) {
  72. // NOTE: full-reload, at least for the moment. Also if backend changed its port then we will get 404 here.
  73. window.location.reload();
  74. };
  75. }());</script>`, conf.LiveReload.Port))
  76. bodyCloseTag := []byte("</body>")
  77. wrapper := func(w http.ResponseWriter, r *http.Request, _ http.HandlerFunc) {
  78. ctx := contextPool.Acquire(w, r)
  79. rec := ctx.Recorder() // Record everything and write all in once at the Context release.
  80. router.ServeHTTPC(ctx) // We directly call request handler with Context.
  81. if strings.HasPrefix(ctx.GetContentType(), "text/html") {
  82. // delete(rec.Header(), context.ContentLengthHeaderKey)
  83. body := rec.Body()
  84. if idx := bytes.LastIndex(body, bodyCloseTag); idx > 0 {
  85. // add the script right before last </body>.
  86. body = append(body[:idx], bytes.Replace(body[idx:], bodyCloseTag, append(scriptReloadJS, bodyCloseTag...), 1)...)
  87. rec.SetBody(body)
  88. } else {
  89. // Just append it.
  90. rec.Write(scriptReloadJS) // nolint:errcheck
  91. }
  92. if _, has := rec.Header()[context.ContentLengthHeaderKey]; has {
  93. rec.Header().Set(context.ContentLengthHeaderKey, fmt.Sprintf("%d", len(rec.Body())))
  94. }
  95. }
  96. contextPool.Release(ctx)
  97. }
  98. router.WrapRouter(wrapper)
  99. return true, nil
  100. }