web.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package web
  2. import (
  3. "bytes"
  4. "context"
  5. "errors"
  6. "fmt"
  7. "html/template"
  8. "log"
  9. "net/http"
  10. "time"
  11. "github.com/spf13/viper"
  12. "github.com/kellegous/go/backend"
  13. "github.com/kellegous/go/internal"
  14. )
  15. // Serve a bundled asset over HTTP.
  16. func serveAsset(w http.ResponseWriter, r *http.Request, name string) {
  17. n, err := AssetInfo(name)
  18. if err != nil {
  19. http.NotFound(w, r)
  20. return
  21. }
  22. a, err := Asset(name)
  23. if err != nil {
  24. http.NotFound(w, r)
  25. return
  26. }
  27. http.ServeContent(w, r, n.Name(), n.ModTime(), bytes.NewReader(a))
  28. }
  29. func templateFromAssetFn(fn func() (*asset, error)) (*template.Template, error) {
  30. a, err := fn()
  31. if err != nil {
  32. return nil, err
  33. }
  34. t := template.New(a.info.Name())
  35. return t.Parse(string(a.bytes))
  36. }
  37. // The default handler responds to most requests. It is responsible for the
  38. // shortcut redirects and for sending unmapped shortcuts to the edit page.
  39. func getDefault(backend backend.Backend, w http.ResponseWriter, r *http.Request) {
  40. p := parseName("/", r.URL.Path)
  41. if p == "" {
  42. http.Redirect(w, r, "/edit/", http.StatusTemporaryRedirect)
  43. return
  44. }
  45. ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
  46. defer cancel()
  47. rt, err := backend.Get(ctx, p)
  48. if errors.Is(err, internal.ErrRouteNotFound) {
  49. http.Redirect(w, r,
  50. fmt.Sprintf("/edit/%s", cleanName(p)),
  51. http.StatusTemporaryRedirect)
  52. return
  53. } else if err != nil {
  54. log.Panic(err)
  55. }
  56. http.Redirect(w, r,
  57. rt.URL,
  58. http.StatusTemporaryRedirect)
  59. }
  60. func getLinks(backend backend.Backend, w http.ResponseWriter, r *http.Request) {
  61. t, err := templateFromAssetFn(linksHtml)
  62. if err != nil {
  63. log.Panic(err)
  64. }
  65. ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
  66. defer cancel()
  67. rts, err := backend.GetAll(ctx)
  68. if err != nil {
  69. log.Panic(err)
  70. }
  71. if err := t.Execute(w, rts); err != nil {
  72. log.Panic(err)
  73. }
  74. }
  75. // ListenAndServe sets up all web routes, binds the port and handles incoming
  76. // web requests.
  77. func ListenAndServe(backend backend.Backend) error {
  78. addr := viper.GetString("addr")
  79. admin := viper.GetBool("admin")
  80. version := viper.GetString("version")
  81. host := viper.GetString("host")
  82. mux := http.NewServeMux()
  83. mux.HandleFunc("/api/url/", func(w http.ResponseWriter, r *http.Request) {
  84. apiURL(backend, host, w, r)
  85. })
  86. mux.HandleFunc("/api/urls/", func(w http.ResponseWriter, r *http.Request) {
  87. apiURLs(backend, host, w, r)
  88. })
  89. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  90. getDefault(backend, w, r)
  91. })
  92. mux.HandleFunc("/edit/", func(w http.ResponseWriter, r *http.Request) {
  93. p := parseName("/edit/", r.URL.Path)
  94. // if this is a banned name, just redirect to the local URI. That'll show em.
  95. if isBannedName(p) {
  96. http.Redirect(w, r, fmt.Sprintf("/%s", p), http.StatusTemporaryRedirect)
  97. return
  98. }
  99. serveAsset(w, r, "edit.html")
  100. })
  101. mux.HandleFunc("/links/", func(w http.ResponseWriter, r *http.Request) {
  102. getLinks(backend, w, r)
  103. })
  104. mux.HandleFunc("/s/", func(w http.ResponseWriter, r *http.Request) {
  105. serveAsset(w, r, r.URL.Path[len("/s/"):])
  106. })
  107. mux.HandleFunc("/version", func(w http.ResponseWriter, r *http.Request) {
  108. fmt.Fprintln(w, version)
  109. })
  110. mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
  111. fmt.Fprintln(w, "👍")
  112. })
  113. // TODO(knorton): Remove the admin handler.
  114. if admin {
  115. mux.Handle("/admin/", &adminHandler{backend})
  116. }
  117. return http.ListenAndServe(addr, mux)
  118. }