| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196 |
- package web
- import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "time"
- "github.com/kellegous/go/context"
- "github.com/syndtr/goleveldb/leveldb"
- )
- const (
- alpha = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
- )
- var (
- errInvalidURL = errors.New("Invalid URL")
- errRedirectLoop = errors.New(" I'm sorry, Dave. I'm afraid I can't do that")
- )
- // A very simple encoding of numeric ids. This is simply a base62 encoding
- // prefixed with ":"
- func encodeID(id uint64) string {
- n := uint64(len(alpha))
- b := make([]byte, 0, 8)
- if id == 0 {
- return "0"
- }
- b = append(b, ':')
- for id > 0 {
- b = append(b, alpha[id%n])
- id /= n
- }
- return string(b)
- }
- // Check that the given URL is suitable as a shortcut link.
- func validateURL(r *http.Request, s string) error {
- u, err := url.Parse(s)
- if err != nil {
- return errInvalidURL
- }
- switch u.Scheme {
- case "http", "https", "mailto", "ftp":
- break
- default:
- return errInvalidURL
- }
- if r.Host == u.Host {
- return errRedirectLoop
- }
- return nil
- }
- func apiURLPost(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
- p := parseName("/api/url/", r.URL.Path)
- var req struct {
- URL string `json:"url"`
- }
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- writeJSONError(w, "invalid json")
- return
- }
- // Handle delete requests
- if req.URL == "" {
- if p == "" {
- writeJSONError(w, "url required")
- return
- }
- if err := ctx.Del(p); err != nil {
- writeJSONBackendError(w, err)
- return
- }
- writeJSONOk(w)
- return
- }
- if err := validateURL(r, req.URL); err != nil {
- writeJSONError(w, err.Error())
- return
- }
- // If no name is specified, an ID must be generate.
- if p == "" {
- id, err := ctx.NextID()
- if err != nil {
- writeJSONBackendError(w, err)
- return
- }
- p = encodeID(id)
- }
- rt := context.Route{
- URL: req.URL,
- Time: time.Now(),
- }
- if err := ctx.Put(p, &rt); err != nil {
- writeJSONBackendError(w, err)
- return
- }
- writeJSONRoute(w, p, &rt)
- }
- func apiURLGet(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
- p := parseName("/api/url/", r.URL.Path)
- if p == "" {
- writeJSONOk(w)
- return
- }
- rt, err := ctx.Get(p)
- if err == leveldb.ErrNotFound {
- writeJSONOk(w)
- return
- } else if err != nil {
- writeJSONBackendError(w, err)
- return
- }
- writeJSONRoute(w, p, rt)
- }
- func apiURLDelete(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
- p := parseName("/api/url/", r.URL.Path)
- if p == "" {
- writeJSONError(w, "name required")
- return
- }
- if err := ctx.Del(p); err == leveldb.ErrNotFound {
- writeJSONError(w, fmt.Sprintf("%s not found", p))
- return
- } else if err != nil {
- writeJSONBackendError(w, err)
- return
- }
- writeJSONOk(w)
- }
- func apiURLsGet(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
- // TODO(knorton): This will allow enumeration of the routes.
- writeJSONError(w, http.StatusText(http.StatusNotImplemented))
- }
- func apiURL(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
- switch r.Method {
- case "POST":
- apiURLPost(ctx, w, r)
- case "GET":
- apiURLGet(ctx, w, r)
- case "DELETE":
- apiURLDelete(ctx, w, r)
- default:
- writeJSONError(w, http.StatusText(http.StatusMethodNotAllowed))
- }
- }
- func apiURLs(ctx *context.Context, w http.ResponseWriter, r *http.Request) {
- switch r.Method {
- case "GET":
- apiURLsGet(ctx, w, r)
- default:
- writeJSONError(w, http.StatusText(http.StatusMethodNotAllowed))
- }
- }
- // Setup ...
- func Setup(m *http.ServeMux, ctx *context.Context) {
- m.HandleFunc("/api/url/", func(w http.ResponseWriter, r *http.Request) {
- apiURL(ctx, w, r)
- })
- m.HandleFunc("/api/urls/", func(w http.ResponseWriter, r *http.Request) {
- apiURLs(ctx, w, r)
- })
- }
|