renderer.go 3.1 KB

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