github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/godoc/short/short.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by the Apache 2.0 3 // license that can be found in the LICENSE file. 4 5 // +build appengine 6 7 // Package short implements a simple URL shortener, serving an administrative 8 // interface at /s and shortened urls from /s/key. 9 // It is designed to run only on the instance of godoc that serves golang.org. 10 package short 11 12 // TODO(adg): collect statistics on URL visits 13 14 import ( 15 "errors" 16 "fmt" 17 "html/template" 18 "net/http" 19 "net/url" 20 "regexp" 21 22 "golang.org/x/net/context" 23 24 "google.golang.org/appengine" 25 "google.golang.org/appengine/datastore" 26 "google.golang.org/appengine/log" 27 "google.golang.org/appengine/memcache" 28 "google.golang.org/appengine/user" 29 ) 30 31 const ( 32 prefix = "/s" 33 kind = "Link" 34 baseURL = "https://golang.org" + prefix 35 ) 36 37 // Link represents a short link. 38 type Link struct { 39 Key, Target string 40 } 41 42 var validKey = regexp.MustCompile(`^[a-zA-Z0-9-_.]+$`) 43 44 func RegisterHandlers(mux *http.ServeMux) { 45 mux.HandleFunc(prefix, adminHandler) 46 mux.HandleFunc(prefix+"/", linkHandler) 47 } 48 49 // linkHandler services requests to short URLs. 50 // http://golang.org/s/key 51 // It consults memcache and datastore for the Link for key. 52 // It then sends a redirects or an error message. 53 func linkHandler(w http.ResponseWriter, r *http.Request) { 54 c := appengine.NewContext(r) 55 56 key := r.URL.Path[len(prefix)+1:] 57 if !validKey.MatchString(key) { 58 http.Error(w, "not found", http.StatusNotFound) 59 return 60 } 61 62 var link Link 63 _, err := memcache.JSON.Get(c, cacheKey(key), &link) 64 if err != nil { 65 k := datastore.NewKey(c, kind, key, 0, nil) 66 err = datastore.Get(c, k, &link) 67 switch err { 68 case datastore.ErrNoSuchEntity: 69 http.Error(w, "not found", http.StatusNotFound) 70 return 71 default: // != nil 72 log.Errorf(c, "%q: %v", key, err) 73 http.Error(w, "internal server error", http.StatusInternalServerError) 74 return 75 case nil: 76 item := &memcache.Item{ 77 Key: cacheKey(key), 78 Object: &link, 79 } 80 if err := memcache.JSON.Set(c, item); err != nil { 81 log.Warningf(c, "%q: %v", key, err) 82 } 83 } 84 } 85 86 http.Redirect(w, r, link.Target, http.StatusFound) 87 } 88 89 var adminTemplate = template.Must(template.New("admin").Parse(templateHTML)) 90 91 // adminHandler serves an administrative interface. 92 func adminHandler(w http.ResponseWriter, r *http.Request) { 93 c := appengine.NewContext(r) 94 95 if !user.IsAdmin(c) { 96 http.Error(w, "forbidden", http.StatusForbidden) 97 return 98 } 99 100 var newLink *Link 101 var doErr error 102 if r.Method == "POST" { 103 key := r.FormValue("key") 104 switch r.FormValue("do") { 105 case "Add": 106 newLink = &Link{key, r.FormValue("target")} 107 doErr = putLink(c, newLink) 108 case "Delete": 109 k := datastore.NewKey(c, kind, key, 0, nil) 110 doErr = datastore.Delete(c, k) 111 default: 112 http.Error(w, "unknown action", http.StatusBadRequest) 113 } 114 err := memcache.Delete(c, cacheKey(key)) 115 if err != nil && err != memcache.ErrCacheMiss { 116 log.Warningf(c, "%q: %v", key, err) 117 } 118 } 119 120 var links []*Link 121 _, err := datastore.NewQuery(kind).Order("Key").GetAll(c, &links) 122 if err != nil { 123 http.Error(w, err.Error(), http.StatusInternalServerError) 124 log.Errorf(c, "%v", err) 125 return 126 } 127 128 // Put the new link in the list if it's not there already. 129 // (Eventual consistency means that it might not show up 130 // immediately, which might be confusing for the user.) 131 if newLink != nil && doErr == nil { 132 found := false 133 for i := range links { 134 if links[i].Key == newLink.Key { 135 found = true 136 break 137 } 138 } 139 if !found { 140 links = append([]*Link{newLink}, links...) 141 } 142 newLink = nil 143 } 144 145 var data = struct { 146 BaseURL string 147 Prefix string 148 Links []*Link 149 New *Link 150 Error error 151 }{baseURL, prefix, links, newLink, doErr} 152 if err := adminTemplate.Execute(w, &data); err != nil { 153 log.Criticalf(c, "adminTemplate: %v", err) 154 } 155 } 156 157 // putLink validates the provided link and puts it into the datastore. 158 func putLink(c context.Context, link *Link) error { 159 if !validKey.MatchString(link.Key) { 160 return errors.New("invalid key; must match " + validKey.String()) 161 } 162 if _, err := url.Parse(link.Target); err != nil { 163 return fmt.Errorf("bad target: %v", err) 164 } 165 k := datastore.NewKey(c, kind, link.Key, 0, nil) 166 _, err := datastore.Put(c, k, link) 167 return err 168 } 169 170 // cacheKey returns a short URL key as a memcache key. 171 func cacheKey(key string) string { 172 return "link-" + key 173 }