renderer.go 3.0 KB

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