web.go 4.5 KB

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