index.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package handlers
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/url"
  8. "path/filepath"
  9. "strings"
  10. "time"
  11. "github.com/owncast/owncast/config"
  12. "github.com/owncast/owncast/core"
  13. "github.com/owncast/owncast/core/cache"
  14. "github.com/owncast/owncast/core/data"
  15. "github.com/owncast/owncast/models"
  16. "github.com/owncast/owncast/static"
  17. "github.com/owncast/owncast/utils"
  18. "github.com/owncast/owncast/webserver/router/middleware"
  19. log "github.com/sirupsen/logrus"
  20. )
  21. var gc = cache.GetGlobalCache()
  22. // IndexHandler handles the default index route.
  23. func IndexHandler(w http.ResponseWriter, r *http.Request) {
  24. middleware.EnableCors(w)
  25. isIndexRequest := r.URL.Path == "/" || filepath.Base(r.URL.Path) == "index.html" || filepath.Base(r.URL.Path) == ""
  26. if utils.IsUserAgentAPlayer(r.UserAgent()) && isIndexRequest {
  27. http.Redirect(w, r, "/hls/stream.m3u8", http.StatusTemporaryRedirect)
  28. return
  29. }
  30. // For search engine bots and social scrapers return a special
  31. // server-rendered page.
  32. if utils.IsUserAgentABot(r.UserAgent()) && isIndexRequest {
  33. handleScraperMetadataPage(w, r)
  34. return
  35. }
  36. // Set a cache control max-age header
  37. middleware.SetCachingHeaders(w, r)
  38. nonceRandom, _ := utils.GenerateRandomString(5)
  39. // Set our global HTTP headers
  40. middleware.SetHeaders(w, fmt.Sprintf("nonce-%s", nonceRandom))
  41. if isIndexRequest {
  42. renderIndexHtml(w, nonceRandom)
  43. return
  44. }
  45. serveWeb(w, r)
  46. }
  47. func renderIndexHtml(w http.ResponseWriter, nonce string) {
  48. type serverSideContent struct {
  49. Name string
  50. Summary string
  51. RequestedURL string
  52. TagsString string
  53. ThumbnailURL string
  54. Thumbnail string
  55. Image string
  56. StatusJSON string
  57. ServerConfigJSON string
  58. EmbedVideo string
  59. Nonce string
  60. }
  61. status := getStatusResponse()
  62. sb, err := json.Marshal(status)
  63. if err != nil {
  64. http.Error(w, err.Error(), http.StatusInternalServerError)
  65. return
  66. }
  67. config := getConfigResponse()
  68. cb, err := json.Marshal(config)
  69. if err != nil {
  70. http.Error(w, err.Error(), http.StatusInternalServerError)
  71. return
  72. }
  73. content := serverSideContent{
  74. Name: data.GetServerName(),
  75. Summary: data.GetServerSummary(),
  76. RequestedURL: fmt.Sprintf("%s%s", data.GetServerURL(), "/"),
  77. TagsString: strings.Join(data.GetServerMetadataTags(), ","),
  78. ThumbnailURL: "thumbnail.jpg",
  79. Thumbnail: "thumbnail.jpg",
  80. Image: "logo/external",
  81. StatusJSON: string(sb),
  82. ServerConfigJSON: string(cb),
  83. EmbedVideo: "embed/video",
  84. Nonce: nonce,
  85. }
  86. index, err := static.GetWebIndexTemplate()
  87. if err != nil {
  88. http.Error(w, err.Error(), http.StatusInternalServerError)
  89. return
  90. }
  91. if err := index.Execute(w, content); err != nil {
  92. http.Error(w, err.Error(), http.StatusInternalServerError)
  93. }
  94. }
  95. // MetadataPage represents a server-rendered web page for bots and web scrapers.
  96. type MetadataPage struct {
  97. RequestedURL string
  98. Image string
  99. Thumbnail string
  100. TagsString string
  101. Summary string
  102. Name string
  103. Tags []string
  104. SocialHandles []models.SocialHandle
  105. }
  106. // Return a basic HTML page with server-rendered metadata from the config
  107. // to give to Opengraph clients and web scrapers (bots, web crawlers, etc).
  108. func handleScraperMetadataPage(w http.ResponseWriter, r *http.Request) {
  109. cacheKey := "bot-scraper-html"
  110. cacheHtmlExpiration := time.Duration(10) * time.Second
  111. c := gc.GetOrCreateCache(cacheKey, cacheHtmlExpiration)
  112. cachedHtml := c.GetValueForKey(cacheKey)
  113. if cachedHtml != nil {
  114. w.Header().Set("Content-Type", "text/html")
  115. _, _ = w.Write(cachedHtml)
  116. return
  117. }
  118. tmpl, err := static.GetBotMetadataTemplate()
  119. if err != nil {
  120. log.Errorln(err)
  121. w.WriteHeader(http.StatusInternalServerError)
  122. return
  123. }
  124. scheme := "http"
  125. if siteURL := data.GetServerURL(); siteURL != "" {
  126. if parsed, err := url.Parse(siteURL); err == nil && parsed.Scheme != "" {
  127. scheme = parsed.Scheme
  128. }
  129. }
  130. fullURL, err := url.Parse(fmt.Sprintf("%s://%s%s", scheme, r.Host, r.URL.Path))
  131. if err != nil {
  132. log.Errorln(err)
  133. }
  134. imageURL, err := url.Parse(fmt.Sprintf("%s://%s%s", scheme, r.Host, "/logo/external"))
  135. if err != nil {
  136. log.Errorln(err)
  137. }
  138. status := core.GetStatus()
  139. // If the thumbnail does not exist or we're offline then just use the logo image
  140. var thumbnailURL string
  141. if status.Online && utils.DoesFileExists(filepath.Join(config.DataDirectory, "tmp", "thumbnail.jpg")) {
  142. thumbnail, err := url.Parse(fmt.Sprintf("%s://%s%s", scheme, r.Host, "/thumbnail.jpg"))
  143. if err != nil {
  144. log.Errorln(err)
  145. thumbnailURL = imageURL.String()
  146. } else {
  147. thumbnailURL = thumbnail.String()
  148. }
  149. } else {
  150. thumbnailURL = imageURL.String()
  151. }
  152. tagsString := strings.Join(data.GetServerMetadataTags(), ",")
  153. metadata := MetadataPage{
  154. Name: data.GetServerName(),
  155. RequestedURL: fullURL.String(),
  156. Image: imageURL.String(),
  157. Summary: data.GetServerSummary(),
  158. Thumbnail: thumbnailURL,
  159. TagsString: tagsString,
  160. Tags: data.GetServerMetadataTags(),
  161. SocialHandles: data.GetSocialHandles(),
  162. }
  163. // Cache the rendered HTML
  164. var b bytes.Buffer
  165. if err := tmpl.Execute(&b, metadata); err != nil {
  166. log.Errorln(err)
  167. }
  168. c.Set(cacheKey, b.Bytes())
  169. // Set a cache header
  170. middleware.SetCachingHeaders(w, r)
  171. w.Header().Set("Content-Type", "text/html")
  172. if _, err = w.Write(b.Bytes()); err != nil {
  173. log.Errorln(err)
  174. }
  175. }