web.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. type routeWithName struct {
  46. Name string `json:"name"`
  47. *context.Route
  48. }
  49. type msg struct {
  50. Ok bool `json:"ok"`
  51. Error string `json:"error,omitempty"`
  52. Route *routeWithName `json:"route,omitempty"`
  53. }
  54. func writeJSON(w http.ResponseWriter, data interface{}, status int) {
  55. w.Header().Set("Content-Type", "application/json;charset=utf-8")
  56. w.WriteHeader(status)
  57. if err := json.NewEncoder(w).Encode(data); err != nil {
  58. log.Panic(err)
  59. }
  60. }
  61. func writeJSONRoute(w http.ResponseWriter, name string, rt *context.Route) {
  62. writeJSON(w, &msg{
  63. Ok: true,
  64. Route: &routeWithName{
  65. Name: name,
  66. Route: rt,
  67. },
  68. }, http.StatusOK)
  69. }
  70. func writeJSONOk(w http.ResponseWriter) {
  71. writeJSON(w, &msg{
  72. Ok: true,
  73. }, http.StatusOK)
  74. }
  75. func writeJSONError(w http.ResponseWriter, err string) {
  76. writeJSON(w, &msg{
  77. Ok: false,
  78. Error: err,
  79. }, http.StatusOK)
  80. }
  81. func writeJSONBackendError(w http.ResponseWriter, err error) {
  82. log.Printf("[error] %s", err)
  83. writeJSONError(w, "backend error")
  84. }
  85. func serveAsset(w http.ResponseWriter, r *http.Request, name string) {
  86. n, err := AssetInfo(name)
  87. if err != nil {
  88. http.NotFound(w, r)
  89. return
  90. }
  91. a, err := Asset(name)
  92. if err != nil {
  93. http.NotFound(w, r)
  94. return
  95. }
  96. http.ServeContent(w, r, n.Name(), n.ModTime(), bytes.NewReader(a))
  97. }
  98. type apiHandler struct {
  99. ctx *context.Context
  100. }
  101. func validURL(s string) bool {
  102. u, err := url.Parse(s)
  103. if err != nil {
  104. return false
  105. }
  106. switch u.Scheme {
  107. case "http", "https", "mailto", "ftp":
  108. return true
  109. }
  110. return false
  111. }
  112. func apiPost(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
  113. p := parseName("/api/url/", r.URL.Path)
  114. var req struct {
  115. URL string `json:"url"`
  116. }
  117. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  118. writeJSONError(w, "invalid json")
  119. return
  120. }
  121. // Handle delete requests
  122. if req.URL == "" {
  123. if p == "" {
  124. writeJSONError(w, "url required")
  125. return
  126. }
  127. if err := ctx.Del(p); err != nil {
  128. writeJSONBackendError(w, err)
  129. return
  130. }
  131. writeJSONOk(w)
  132. return
  133. }
  134. if !validURL(req.URL) {
  135. writeJSONError(w, "invalid URL")
  136. return
  137. }
  138. // If no name is specified, an ID must be generate.
  139. if p == "" {
  140. id, err := ctx.NextID()
  141. if err != nil {
  142. writeJSONBackendError(w, err)
  143. return
  144. }
  145. p = encodeID(id)
  146. }
  147. rt := context.Route{
  148. URL: req.URL,
  149. Time: time.Now(),
  150. }
  151. if err := ctx.Put(p, &rt); err != nil {
  152. writeJSONBackendError(w, err)
  153. return
  154. }
  155. writeJSONRoute(w, p, &rt)
  156. }
  157. func apiGet(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
  158. p := parseName("/api/url/", r.URL.Path)
  159. if p == "" {
  160. writeJSONOk(w)
  161. return
  162. }
  163. rt, err := ctx.Get(p)
  164. if err == leveldb.ErrNotFound {
  165. writeJSONOk(w)
  166. return
  167. } else if err != nil {
  168. writeJSONBackendError(w, err)
  169. return
  170. }
  171. writeJSONRoute(w, p, rt)
  172. }
  173. func (h *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  174. switch r.Method {
  175. case "POST":
  176. apiPost(h.ctx, w, r)
  177. case "GET":
  178. apiGet(h.ctx, w, r)
  179. default:
  180. writeJSONError(w, http.StatusText(http.StatusMethodNotAllowed))
  181. }
  182. }
  183. type defaultHandler struct {
  184. ctx *context.Context
  185. }
  186. func (h *defaultHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  187. p := parseName("/", r.URL.Path)
  188. if p == "" {
  189. http.Redirect(w, r, "/edit/", http.StatusTemporaryRedirect)
  190. return
  191. }
  192. rt, err := h.ctx.Get(p)
  193. if err == leveldb.ErrNotFound {
  194. http.Redirect(w, r,
  195. fmt.Sprintf("/edit/%s", cleanName(p)),
  196. http.StatusTemporaryRedirect)
  197. return
  198. } else if err != nil {
  199. log.Panic(err)
  200. }
  201. http.Redirect(w, r,
  202. rt.URL,
  203. http.StatusTemporaryRedirect)
  204. }
  205. type editHandler struct {
  206. ctx *context.Context
  207. }
  208. func (h *editHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  209. serveAsset(w, r, "index.html")
  210. }
  211. // ListenAndServe ...
  212. func ListenAndServe(addr string, ctx *context.Context) error {
  213. mux := http.NewServeMux()
  214. mux.Handle("/", &defaultHandler{ctx})
  215. mux.Handle("/edit/", &editHandler{ctx})
  216. mux.Handle("/api/url/", &apiHandler{ctx})
  217. mux.HandleFunc("/s/", func(w http.ResponseWriter, r *http.Request) {
  218. serveAsset(w, r, r.URL.Path[len("/s/"):])
  219. })
  220. return http.ListenAndServe(addr, mux)
  221. }