renderer.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package renderer
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "os"
  7. "os/exec"
  8. "path/filepath"
  9. "runtime"
  10. "time"
  11. "strconv"
  12. "strings"
  13. "github.com/grafana/grafana/pkg/log"
  14. "github.com/grafana/grafana/pkg/middleware"
  15. "github.com/grafana/grafana/pkg/setting"
  16. "github.com/grafana/grafana/pkg/util"
  17. )
  18. type RenderOpts struct {
  19. Path string
  20. Width string
  21. Height string
  22. Timeout string
  23. OrgId int64
  24. Timezone string
  25. }
  26. var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
  27. var rendererLog log.Logger = log.New("png-renderer")
  28. func isoTimeOffsetToPosixTz(isoOffset string) string {
  29. // invert offset
  30. if strings.HasPrefix(isoOffset, "UTC+") {
  31. return strings.Replace(isoOffset, "UTC+", "UTC-", 1)
  32. }
  33. if strings.HasPrefix(isoOffset, "UTC-") {
  34. return strings.Replace(isoOffset, "UTC-", "UTC+", 1)
  35. }
  36. return isoOffset
  37. }
  38. func appendEnviron(baseEnviron []string, name string, value string) []string {
  39. results := make([]string, 0)
  40. prefix := fmt.Sprintf("%s=", name)
  41. for _, v := range baseEnviron {
  42. if !strings.HasPrefix(v, prefix) {
  43. results = append(results, v)
  44. }
  45. }
  46. return append(results, fmt.Sprintf("%s=%s", name, value))
  47. }
  48. func RenderToPng(params *RenderOpts) (string, error) {
  49. rendererLog.Info("Rendering", "path", params.Path)
  50. var executable = "phantomjs"
  51. if runtime.GOOS == "windows" {
  52. executable = executable + ".exe"
  53. }
  54. localDomain := "localhost"
  55. if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
  56. localDomain = setting.HttpAddr
  57. }
  58. url := fmt.Sprintf("%s://%s:%s/%s", setting.Protocol, localDomain, setting.HttpPort, params.Path)
  59. binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
  60. scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
  61. pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
  62. pngPath = pngPath + ".png"
  63. renderKey := middleware.AddRenderAuthKey(params.OrgId)
  64. defer middleware.RemoveRenderAuthKey(renderKey)
  65. timeout, err := strconv.Atoi(params.Timeout)
  66. if err != nil {
  67. timeout = 15
  68. }
  69. cmdArgs := []string{
  70. "--ignore-ssl-errors=true",
  71. "--web-security=false",
  72. scriptPath,
  73. "url=" + url,
  74. "width=" + params.Width,
  75. "height=" + params.Height,
  76. "png=" + pngPath,
  77. "domain=" + localDomain,
  78. "timeout=" + strconv.Itoa(timeout),
  79. "renderKey=" + renderKey,
  80. }
  81. cmd := exec.Command(binPath, cmdArgs...)
  82. stdout, err := cmd.StdoutPipe()
  83. if err != nil {
  84. return "", err
  85. }
  86. stderr, err := cmd.StderrPipe()
  87. if err != nil {
  88. return "", err
  89. }
  90. if params.Timezone != "" {
  91. baseEnviron := os.Environ()
  92. cmd.Env = appendEnviron(baseEnviron, "TZ", isoTimeOffsetToPosixTz(params.Timezone))
  93. }
  94. err = cmd.Start()
  95. if err != nil {
  96. return "", err
  97. }
  98. go io.Copy(os.Stdout, stdout)
  99. go io.Copy(os.Stdout, stderr)
  100. done := make(chan error)
  101. go func() {
  102. if err := cmd.Wait(); err != nil {
  103. rendererLog.Error("failed to render an image", "error", err)
  104. }
  105. close(done)
  106. }()
  107. select {
  108. case <-time.After(time.Duration(timeout) * time.Second):
  109. if err := cmd.Process.Kill(); err != nil {
  110. rendererLog.Error("failed to kill", "error", err)
  111. }
  112. return "", ErrTimeout
  113. case <-done:
  114. }
  115. rendererLog.Debug("Image rendered", "path", pngPath)
  116. return pngPath, nil
  117. }