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  }