web.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package web
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "encoding/binary"
  6. "encoding/json"
  7. "fmt"
  8. "log"
  9. "math/rand"
  10. "net/http"
  11. "strings"
  12. "time"
  13. "github.com/kellegous/go/context"
  14. "github.com/syndtr/goleveldb/leveldb"
  15. )
  16. func makeName() string {
  17. var buf bytes.Buffer
  18. binary.Write(&buf, binary.LittleEndian, rand.Int63())
  19. return base64.URLEncoding.EncodeToString(buf.Bytes())
  20. }
  21. func parseName(base, path string) string {
  22. t := path[len(base):]
  23. ix := strings.Index(t, "/")
  24. if ix == -1 {
  25. return t
  26. }
  27. return t[:ix]
  28. }
  29. func writeJSON(w http.ResponseWriter, data interface{}, status int) {
  30. w.Header().Set("Content-Type", "application/json;charset=utf-8")
  31. if err := json.NewEncoder(w).Encode(data); err != nil {
  32. log.Panic(err)
  33. }
  34. }
  35. func writeJSONError(w http.ResponseWriter, error string, status int) {
  36. writeJSON(w, map[string]interface{}{
  37. "error": error,
  38. }, status)
  39. }
  40. func writeJSONRoute(w http.ResponseWriter, name string, rt *context.Route) {
  41. res := struct {
  42. Name string `json:"name"`
  43. URL string `json:"url"`
  44. Time time.Time `json:"time"`
  45. }{
  46. name,
  47. rt.URL,
  48. rt.Time,
  49. }
  50. writeJSON(w, &res, http.StatusOK)
  51. }
  52. func serveAsset(w http.ResponseWriter, r *http.Request, name string) {
  53. n, err := AssetInfo(name)
  54. if err != nil {
  55. http.NotFound(w, r)
  56. return
  57. }
  58. a, err := Asset(name)
  59. if err != nil {
  60. http.NotFound(w, r)
  61. return
  62. }
  63. http.ServeContent(w, r, n.Name(), n.ModTime(), bytes.NewReader(a))
  64. }
  65. type apiHandler struct {
  66. ctx *context.Context
  67. }
  68. func (h *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  69. p := parseName("/api/url/", r.URL.Path)
  70. if p == "" {
  71. writeJSONError(w,
  72. http.StatusText(http.StatusNotFound),
  73. http.StatusNotFound)
  74. return
  75. }
  76. if r.Method == "POST" {
  77. var req struct {
  78. URL string `json:"url"`
  79. }
  80. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  81. writeJSONError(w, "invalid json", http.StatusBadRequest)
  82. return
  83. }
  84. if req.URL == "" {
  85. writeJSONError(w, "url required", http.StatusBadRequest)
  86. return
  87. }
  88. rt := context.Route{
  89. URL: req.URL,
  90. Time: time.Now(),
  91. }
  92. if err := h.ctx.Put(p, &rt); err != nil {
  93. log.Panic(err)
  94. }
  95. writeJSONRoute(w, p, &rt)
  96. } else if r.Method == "GET" {
  97. rt, err := h.ctx.Get(p)
  98. if err == leveldb.ErrNotFound {
  99. writeJSONError(w, "no such route", http.StatusNotFound)
  100. return
  101. } else if err != nil {
  102. log.Panic(err)
  103. }
  104. writeJSONRoute(w, p, rt)
  105. } else {
  106. writeJSONError(w,
  107. http.StatusText(http.StatusMethodNotAllowed),
  108. http.StatusMethodNotAllowed)
  109. }
  110. }
  111. type defaultHandler struct {
  112. ctx *context.Context
  113. }
  114. func (h *defaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  115. p := parseName("/", r.URL.Path)
  116. if p == "" {
  117. http.Redirect(w, r,
  118. fmt.Sprintf("/edit/%s", makeName()),
  119. http.StatusTemporaryRedirect)
  120. return
  121. }
  122. rt, err := h.ctx.Get(p)
  123. if err == leveldb.ErrNotFound {
  124. http.Redirect(w, r,
  125. fmt.Sprintf("/edit/%s", p),
  126. http.StatusTemporaryRedirect)
  127. return
  128. } else if err != nil {
  129. log.Panic(err)
  130. }
  131. http.Redirect(w, r,
  132. rt.URL,
  133. http.StatusTemporaryRedirect)
  134. }
  135. type editHandler struct {
  136. ctx *context.Context
  137. }
  138. func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  139. p := parseName("/edit/", r.URL.Path)
  140. if p == "" {
  141. http.Redirect(w, r,
  142. fmt.Sprintf("/edit/%s", makeName()),
  143. http.StatusTemporaryRedirect)
  144. return
  145. }
  146. serveAsset(w, r, "index.html")
  147. }
  148. // ListenAndServe ...
  149. func ListenAndServe(addr string, ctx *context.Context) error {
  150. mux := http.NewServeMux()
  151. mux.Handle("/", &defaultHandler{ctx})
  152. mux.Handle("/edit/", &editHandler{ctx})
  153. mux.Handle("/api/url/", &apiHandler{ctx})
  154. mux.HandleFunc("/s/", func(w http.ResponseWriter, r *http.Request) {
  155. serveAsset(w, r, r.URL.Path[len("/s/"):])
  156. })
  157. return http.ListenAndServe(addr, mux)
  158. }