web.go 3.9 KB

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