web.go 4.0 KB

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