|
- // Copyright 2017 The Go Authors. All rights reserved.
- //
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file or at
- // https://developers.google.com/open-source/licenses/bsd.
-
- // Command gddo-server is the GoPkgDoc server.
- package main
-
- import (
- "bytes"
- "crypto/md5"
- "encoding/json"
- "errors"
- "fmt"
- "go/build"
- "html/template"
- "io"
- "log"
- "net/http"
- "os"
- "path"
- "regexp"
- "runtime/debug"
- "sort"
- "strconv"
- "strings"
- "time"
-
- "cloud.google.com/go/compute/metadata"
- "cloud.google.com/go/logging"
- "github.com/spf13/viper"
- "golang.org/x/net/context"
- "google.golang.org/appengine"
-
- "github.com/golang/gddo/database"
- "github.com/golang/gddo/doc"
- "github.com/golang/gddo/gosrc"
- "github.com/golang/gddo/httputil"
- )
-
- const (
- jsonMIMEType = "application/json; charset=utf-8"
- textMIMEType = "text/plain; charset=utf-8"
- htmlMIMEType = "text/html; charset=utf-8"
- )
-
- var errUpdateTimeout = errors.New("refresh timeout")
-
- type httpError struct {
- status int // HTTP status code.
- err error // Optional reason for the HTTP error.
- }
-
- func (err *httpError) Error() string {
- if err.err != nil {
- return fmt.Sprintf("status %d, reason %s", err.status, err.err.Error())
- }
- return fmt.Sprintf("Status %d", err.status)
- }
-
- const (
- humanRequest = iota
- robotRequest
- queryRequest
- refreshRequest
- apiRequest
- )
-
- type crawlResult struct {
- pdoc *doc.Package
- err error
- }
-
- // getDoc gets the package documentation from the database or from the version
- // control system as needed.
- func getDoc(path string, requestType int) (*doc.Package, []database.Package, error) {
- if path == "-" {
- // A hack in the database package uses the path "-" to represent the
- // next document to crawl. Block "-" here so that requests to /- always
- // return not found.
- return nil, nil, &httpError{status: http.StatusNotFound}
- }
-
- pdoc, pkgs, nextCrawl, err := db.Get(path)
- if err != nil {
- return nil, nil, err
- }
-
- needsCrawl := false
- switch requestType {
- case queryRequest, apiRequest:
- needsCrawl = nextCrawl.IsZero() && len(pkgs) == 0
- case humanRequest:
- needsCrawl = nextCrawl.Before(time.Now())
- case robotRequest:
- needsCrawl = nextCrawl.IsZero() && len(pkgs) > 0
- }
-
- if !needsCrawl {
- return pdoc, pkgs, nil
- }
-
- c := make(chan crawlResult, 1)
- go func() {
- pdoc, err := crawlDoc("web ", path, pdoc, len(pkgs) > 0, nextCrawl)
- c <- crawlResult{pdoc, err}
- }()
-
- timeout := viper.GetDuration(ConfigGetTimeout)
- if pdoc == nil {
- timeout = viper.GetDuration(ConfigFirstGetTimeout)
- }
-
- select {
- case cr := <-c:
- err = cr.err
- if err == nil {
- pdoc = cr.pdoc
- }
- case <-time.After(timeout):
- err = errUpdateTimeout
- }
-
- switch {
- case err == nil:
- return pdoc, pkgs, nil
- case gosrc.IsNotFound(err):
- return nil, nil, err
- case pdoc != nil:
- log.Printf("Serving %q from database after error getting doc: %v", path, err)
- return pdoc, pkgs, nil
- case err == errUpdateTimeout:
- log.Printf("Serving %q as not found after timeout getting doc", path)
- return nil, nil, &httpError{status: http.StatusNotFound}
- default:
- return nil, nil, err
- }
- }
-
- func templateExt(req *http.Request) string {
- if httputil.NegotiateContentType(req, []string{"text/html", "text/plain"}, "text/html") == "text/plain" {
- return ".txt"
- }
- return ".html"
- }
-
- var (
- robotPat = regexp.MustCompile(`(:?\+https?://)|(?:\Wbot\W)|(?:^Python-urllib)|(?:^Go )|(?:^Java/)`)
- )
-
- func isRobot(req *http.Request) bool {
- if robotPat.MatchString(req.Header.Get("User-Agent")) {
- return true
- }
- host := httputil.StripPort(req.RemoteAddr)
- n, err := db.IncrementCounter(host, 1)
- if err != nil {
- log.Printf("error incrementing counter for %s, %v", host, err)
- return false
- }
- if n > viper.GetFloat64(ConfigRobotThreshold) {
- log.Printf("robot %.2f %s %s", n, host, req.Header.Get("User-Agent"))
- return true
- }
- return false
- }
-
- func popularLinkReferral(req *http.Request) bool {
- return strings.HasSuffix(req.Header.Get("Referer"), "//"+req.Host+"/")
- }
-
- func isView(req *http.Request, key string) bool {
- rq := req.URL.RawQuery
- return strings.HasPrefix(rq, key) &&
- (len(rq) == len(key) || rq[len(key)] == '=' || rq[len(key)] == '&')
- }
-
- // httpEtag returns the package entity tag used in HTTP transactions.
- func httpEtag(pdoc *doc.Package, pkgs []database.Package, importerCount int, flashMessages []flashMessage) string {
- b := make([]byte, 0, 128)
- b = strconv.AppendInt(b, pdoc.Updated.Unix(), 16)
- b = append(b, 0)
- b = append(b, pdoc.Etag...)
- if importerCount >= 8 {
- importerCount = 8
- }
- b = append(b, 0)
- b = strconv.AppendInt(b, int64(importerCount), 16)
- for _, pkg := range pkgs {
- b = append(b, 0)
- b = append(b, pkg.Path...)
- b = append(b, 0)
- b = append(b, pkg.Synopsis...)
- }
- if viper.GetBool(ConfigSidebar) {
- b = append(b, "\000xsb"...)
- }
- for _, m := range flashMessages {
- b = append(b, 0)
- b = append(b, m.ID...)
- for _, a := range m.Args {
- b = append(b, 1)
- b = append(b, a...)
- }
- }
- h := md5.New()
- h.Write(b)
- b = h.Sum(b[:0])
- return fmt.Sprintf("\"%x\"", b)
- }
-
- func servePackage(resp http.ResponseWriter, req *http.Request) error {
- p := path.Clean(req.URL.Path)
- if strings.HasPrefix(p, "/pkg/") {
- p = p[len("/pkg"):]
- }
- if p != req.URL.Path {
- http.Redirect(resp, req, p, http.StatusMovedPermanently)
- return nil
- }
-
- if isView(req, "status.svg") {
- statusImageHandlerSVG.ServeHTTP(resp, req)
- return nil
- }
-
- if isView(req, "status.png") {
- statusImageHandlerPNG.ServeHTTP(resp, req)
- return nil
- }
-
- requestType := humanRequest
- if isRobot(req) {
- requestType = robotRequest
- }
-
- importPath := strings.TrimPrefix(req.URL.Path, "/")
- pdoc, pkgs, err := getDoc(importPath, requestType)
-
- if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
- // To prevent dumb clients from following redirect loops, respond with
- // status 404 if the target document is not found.
- if _, _, err := getDoc(e.Redirect, requestType); gosrc.IsNotFound(err) {
- return &httpError{status: http.StatusNotFound}
- }
- u := "/" + e.Redirect
- if req.URL.RawQuery != "" {
- u += "?" + req.URL.RawQuery
- }
- setFlashMessages(resp, []flashMessage{{ID: "redir", Args: []string{importPath}}})
- http.Redirect(resp, req, u, http.StatusFound)
- return nil
- }
- if err != nil {
- return err
- }
-
- flashMessages := getFlashMessages(resp, req)
-
- if pdoc == nil {
- if len(pkgs) == 0 {
- return &httpError{status: http.StatusNotFound}
- }
- pdocChild, _, _, err := db.Get(pkgs[0].Path)
- if err != nil {
- return err
- }
- pdoc = &doc.Package{
- ProjectName: pdocChild.ProjectName,
- ProjectRoot: pdocChild.ProjectRoot,
- ProjectURL: pdocChild.ProjectURL,
- ImportPath: importPath,
- }
- }
-
- switch {
- case len(req.Form) == 0:
- importerCount := 0
- if pdoc.Name != "" {
- importerCount, err = db.ImporterCount(importPath)
- if err != nil {
- return err
- }
- }
-
- etag := httpEtag(pdoc, pkgs, importerCount, flashMessages)
- status := http.StatusOK
- if req.Header.Get("If-None-Match") == etag {
- status = http.StatusNotModified
- }
-
- if requestType == humanRequest &&
- pdoc.Name != "" && // not a directory
- pdoc.ProjectRoot != "" && // not a standard package
- !pdoc.IsCmd &&
- len(pdoc.Errors) == 0 &&
- !popularLinkReferral(req) {
- if err := db.IncrementPopularScore(pdoc.ImportPath); err != nil {
- log.Printf("ERROR db.IncrementPopularScore(%s): %v", pdoc.ImportPath, err)
- }
- }
- if gceLogger != nil {
- gceLogger.LogEvent(resp, req, nil)
- }
-
- template := "dir"
- switch {
- case pdoc.IsCmd:
- template = "cmd"
- case pdoc.Name != "":
- template = "pkg"
- }
- template += templateExt(req)
-
- return executeTemplate(resp, template, status, http.Header{"Etag": {etag}}, map[string]interface{}{
- "flashMessages": flashMessages,
- "pkgs": pkgs,
- "pdoc": newTDoc(pdoc),
- "importerCount": importerCount,
- })
- case isView(req, "imports"):
- if pdoc.Name == "" {
- break
- }
- pkgs, err = db.Packages(pdoc.Imports)
- if err != nil {
- return err
- }
- return executeTemplate(resp, "imports.html", http.StatusOK, nil, map[string]interface{}{
- "flashMessages": flashMessages,
- "pkgs": pkgs,
- "pdoc": newTDoc(pdoc),
- })
- case isView(req, "tools"):
- proto := "http"
- if req.Host == "godoc.org" {
- proto = "https"
- }
- return executeTemplate(resp, "tools.html", http.StatusOK, nil, map[string]interface{}{
- "flashMessages": flashMessages,
- "uri": fmt.Sprintf("%s://%s/%s", proto, req.Host, importPath),
- "pdoc": newTDoc(pdoc),
- })
- case isView(req, "importers"):
- if pdoc.Name == "" {
- break
- }
- pkgs, err = db.Importers(importPath)
- if err != nil {
- return err
- }
- template := "importers.html"
- if requestType == robotRequest {
- // Hide back links from robots.
- template = "importers_robot.html"
- }
- return executeTemplate(resp, template, http.StatusOK, nil, map[string]interface{}{
- "flashMessages": flashMessages,
- "pkgs": pkgs,
- "pdoc": newTDoc(pdoc),
- })
- case isView(req, "import-graph"):
- if requestType == robotRequest {
- return &httpError{status: http.StatusForbidden}
- }
- if pdoc.Name == "" {
- break
- }
- hide := database.ShowAllDeps
- switch req.Form.Get("hide") {
- case "1":
- hide = database.HideStandardDeps
- case "2":
- hide = database.HideStandardAll
- }
- pkgs, edges, err := db.ImportGraph(pdoc, hide)
- if err != nil {
- return err
- }
- b, err := renderGraph(pdoc, pkgs, edges)
- if err != nil {
- return err
- }
- return executeTemplate(resp, "graph.html", http.StatusOK, nil, map[string]interface{}{
- "flashMessages": flashMessages,
- "svg": template.HTML(b),
- "pdoc": newTDoc(pdoc),
- "hide": hide,
- })
- case isView(req, "play"):
- u, err := playURL(pdoc, req.Form.Get("play"), req.Header.Get("X-AppEngine-Country"))
- if err != nil {
- return err
- }
- http.Redirect(resp, req, u, http.StatusMovedPermanently)
- return nil
- case req.Form.Get("view") != "":
- // Redirect deprecated view= queries.
- var q string
- switch view := req.Form.Get("view"); view {
- case "imports", "importers":
- q = view
- case "import-graph":
- if req.Form.Get("hide") == "1" {
- q = "import-graph&hide=1"
- } else {
- q = "import-graph"
- }
- }
- if q != "" {
- u := *req.URL
- u.RawQuery = q
- http.Redirect(resp, req, u.String(), http.StatusMovedPermanently)
- return nil
- }
- }
- return &httpError{status: http.StatusNotFound}
- }
-
- func serveRefresh(resp http.ResponseWriter, req *http.Request) error {
- importPath := req.Form.Get("path")
- _, pkgs, _, err := db.Get(importPath)
- if err != nil {
- return err
- }
- c := make(chan error, 1)
- go func() {
- _, err := crawlDoc("rfrsh", importPath, nil, len(pkgs) > 0, time.Time{})
- c <- err
- }()
- select {
- case err = <-c:
- case <-time.After(viper.GetDuration(ConfigGetTimeout)):
- err = errUpdateTimeout
- }
- if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
- setFlashMessages(resp, []flashMessage{{ID: "redir", Args: []string{importPath}}})
- importPath = e.Redirect
- err = nil
- } else if err != nil {
- setFlashMessages(resp, []flashMessage{{ID: "refresh", Args: []string{errorText(err)}}})
- }
- http.Redirect(resp, req, "/"+importPath, http.StatusFound)
- return nil
- }
-
- func serveGoIndex(resp http.ResponseWriter, req *http.Request) error {
- pkgs, err := db.GoIndex()
- if err != nil {
- return err
- }
- return executeTemplate(resp, "std.html", http.StatusOK, nil, map[string]interface{}{
- "pkgs": pkgs,
- })
- }
-
- func serveGoSubrepoIndex(resp http.ResponseWriter, req *http.Request) error {
- pkgs, err := db.GoSubrepoIndex()
- if err != nil {
- return err
- }
- return executeTemplate(resp, "subrepo.html", http.StatusOK, nil, map[string]interface{}{
- "pkgs": pkgs,
- })
- }
-
- func runReindex(resp http.ResponseWriter, req *http.Request) {
- fmt.Fprintln(resp, "Reindexing...")
- go reindex()
- }
-
- func runPurgeIndex(resp http.ResponseWriter, req *http.Request) {
- fmt.Fprintln(resp, "Purging the search index...")
- go purgeIndex()
- }
-
- type byPath struct {
- pkgs []database.Package
- rank []int
- }
-
- func (bp *byPath) Len() int { return len(bp.pkgs) }
- func (bp *byPath) Less(i, j int) bool { return bp.pkgs[i].Path < bp.pkgs[j].Path }
- func (bp *byPath) Swap(i, j int) {
- bp.pkgs[i], bp.pkgs[j] = bp.pkgs[j], bp.pkgs[i]
- bp.rank[i], bp.rank[j] = bp.rank[j], bp.rank[i]
- }
-
- type byRank struct {
- pkgs []database.Package
- rank []int
- }
-
- func (br *byRank) Len() int { return len(br.pkgs) }
- func (br *byRank) Less(i, j int) bool { return br.rank[i] < br.rank[j] }
- func (br *byRank) Swap(i, j int) {
- br.pkgs[i], br.pkgs[j] = br.pkgs[j], br.pkgs[i]
- br.rank[i], br.rank[j] = br.rank[j], br.rank[i]
- }
-
- func popular() ([]database.Package, error) {
- const n = 25
-
- pkgs, err := db.Popular(2 * n)
- if err != nil {
- return nil, err
- }
-
- rank := make([]int, len(pkgs))
- for i := range pkgs {
- rank[i] = i
- }
-
- sort.Sort(&byPath{pkgs, rank})
-
- j := 0
- prev := "."
- for i, pkg := range pkgs {
- if strings.HasPrefix(pkg.Path, prev) {
- if rank[j-1] < rank[i] {
- rank[j-1] = rank[i]
- }
- continue
- }
- prev = pkg.Path + "/"
- pkgs[j] = pkg
- rank[j] = rank[i]
- j++
- }
- pkgs = pkgs[:j]
-
- sort.Sort(&byRank{pkgs, rank})
-
- if len(pkgs) > n {
- pkgs = pkgs[:n]
- }
-
- sort.Sort(&byPath{pkgs, rank})
-
- return pkgs, nil
- }
-
- func serveHome(resp http.ResponseWriter, req *http.Request) error {
- if req.URL.Path != "/" {
- return servePackage(resp, req)
- }
-
- q := strings.TrimSpace(req.Form.Get("q"))
- if q == "" {
- pkgs, err := popular()
- if err != nil {
- return err
- }
-
- return executeTemplate(resp, "home"+templateExt(req), http.StatusOK, nil,
- map[string]interface{}{"Popular": pkgs})
- }
-
- if path, ok := isBrowseURL(q); ok {
- q = path
- }
-
- if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
- pdoc, pkgs, err := getDoc(q, queryRequest)
- if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
- http.Redirect(resp, req, "/"+e.Redirect, http.StatusFound)
- return nil
- }
- if err == nil && (pdoc != nil || len(pkgs) > 0) {
- http.Redirect(resp, req, "/"+q, http.StatusFound)
- return nil
- }
- }
-
- ctx := appengine.NewContext(req)
- pkgs, err := database.Search(ctx, q)
- if err != nil {
- return err
- }
- if gceLogger != nil {
- // Log up to top 10 packages we served upon a search.
- logPkgs := pkgs
- if len(pkgs) > 10 {
- logPkgs = pkgs[:10]
- }
- gceLogger.LogEvent(resp, req, logPkgs)
- }
-
- return executeTemplate(resp, "results"+templateExt(req), http.StatusOK, nil,
- map[string]interface{}{"q": q, "pkgs": pkgs})
- }
-
- func serveAbout(resp http.ResponseWriter, req *http.Request) error {
- return executeTemplate(resp, "about.html", http.StatusOK, nil,
- map[string]interface{}{"Host": req.Host})
- }
-
- func serveBot(resp http.ResponseWriter, req *http.Request) error {
- return executeTemplate(resp, "bot.html", http.StatusOK, nil, nil)
- }
-
- func serveHealthCheck(resp http.ResponseWriter, req *http.Request) {
- resp.Write([]byte("Health check: ok\n"))
- }
-
- func logError(req *http.Request, err error, rv interface{}) {
- if err != nil {
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "Error serving %s: %v\n", req.URL, err)
- if rv != nil {
- fmt.Fprintln(&buf, rv)
- buf.Write(debug.Stack())
- }
- log.Print(buf.String())
- }
- }
-
- func serveAPISearch(resp http.ResponseWriter, req *http.Request) error {
- q := strings.TrimSpace(req.Form.Get("q"))
-
- var pkgs []database.Package
-
- if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
- pdoc, _, err := getDoc(q, apiRequest)
- if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
- pdoc, _, err = getDoc(e.Redirect, robotRequest)
- }
- if err == nil && pdoc != nil {
- pkgs = []database.Package{{Path: pdoc.ImportPath, Synopsis: pdoc.Synopsis}}
- }
- }
-
- if pkgs == nil {
- var err error
- ctx := appengine.NewContext(req)
- pkgs, err = database.Search(ctx, q)
- if err != nil {
- return err
- }
- }
-
- var data = struct {
- Results []database.Package `json:"results"`
- }{
- pkgs,
- }
- resp.Header().Set("Content-Type", jsonMIMEType)
- return json.NewEncoder(resp).Encode(&data)
- }
-
- func serveAPIPackages(resp http.ResponseWriter, req *http.Request) error {
- pkgs, err := db.AllPackages()
- if err != nil {
- return err
- }
- data := struct {
- Results []database.Package `json:"results"`
- }{
- pkgs,
- }
- resp.Header().Set("Content-Type", jsonMIMEType)
- return json.NewEncoder(resp).Encode(&data)
- }
-
- func serveAPIImporters(resp http.ResponseWriter, req *http.Request) error {
- importPath := strings.TrimPrefix(req.URL.Path, "/importers/")
- pkgs, err := db.Importers(importPath)
- if err != nil {
- return err
- }
- data := struct {
- Results []database.Package `json:"results"`
- }{
- pkgs,
- }
- resp.Header().Set("Content-Type", jsonMIMEType)
- return json.NewEncoder(resp).Encode(&data)
- }
-
- func serveAPIImports(resp http.ResponseWriter, req *http.Request) error {
- importPath := strings.TrimPrefix(req.URL.Path, "/imports/")
- pdoc, _, err := getDoc(importPath, robotRequest)
- if err != nil {
- return err
- }
- if pdoc == nil || pdoc.Name == "" {
- return &httpError{status: http.StatusNotFound}
- }
- imports, err := db.Packages(pdoc.Imports)
- if err != nil {
- return err
- }
- testImports, err := db.Packages(pdoc.TestImports)
- if err != nil {
- return err
- }
- data := struct {
- Imports []database.Package `json:"imports"`
- TestImports []database.Package `json:"testImports"`
- }{
- imports,
- testImports,
- }
- resp.Header().Set("Content-Type", jsonMIMEType)
- return json.NewEncoder(resp).Encode(&data)
- }
-
- func serveAPIHome(resp http.ResponseWriter, req *http.Request) error {
- return &httpError{status: http.StatusNotFound}
- }
-
- func runHandler(resp http.ResponseWriter, req *http.Request,
- fn func(resp http.ResponseWriter, req *http.Request) error, errfn httputil.Error) {
- defer func() {
- if rv := recover(); rv != nil {
- err := errors.New("handler panic")
- logError(req, err, rv)
- errfn(resp, req, http.StatusInternalServerError, err)
- }
- }()
-
- // TODO(stephenmw): choose headers based on if we are on App Engine
- if viper.GetBool(ConfigTrustProxyHeaders) {
- // If running on GAE, use X-Appengine-User-Ip to identify real ip of requests.
- if s := req.Header.Get("X-Appengine-User-Ip"); s != "" {
- req.RemoteAddr = s
- } else if s := req.Header.Get("X-Real-Ip"); s != "" {
- req.RemoteAddr = s
- }
- }
-
- req.Body = http.MaxBytesReader(resp, req.Body, 2048)
- req.ParseForm()
- var rb httputil.ResponseBuffer
- err := fn(&rb, req)
- if err == nil {
- rb.WriteTo(resp)
- } else if e, ok := err.(*httpError); ok {
- if e.status >= 500 {
- logError(req, err, nil)
- }
- errfn(resp, req, e.status, e.err)
- } else if gosrc.IsNotFound(err) {
- errfn(resp, req, http.StatusNotFound, nil)
- } else {
- logError(req, err, nil)
- errfn(resp, req, http.StatusInternalServerError, err)
- }
- }
-
- type handler func(resp http.ResponseWriter, req *http.Request) error
-
- func (h handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
- runHandler(resp, req, h, handleError)
- }
-
- type apiHandler func(resp http.ResponseWriter, req *http.Request) error
-
- func (h apiHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
- runHandler(resp, req, h, handleAPIError)
- }
-
- func errorText(err error) string {
- if err == errUpdateTimeout {
- return "Timeout getting package files from the version control system."
- }
- if e, ok := err.(*gosrc.RemoteError); ok {
- return "Error getting package files from " + e.Host + "."
- }
- return "Internal server error."
- }
-
- func handleError(resp http.ResponseWriter, req *http.Request, status int, err error) {
- switch status {
- case http.StatusNotFound:
- executeTemplate(resp, "notfound"+templateExt(req), status, nil, map[string]interface{}{
- "flashMessages": getFlashMessages(resp, req),
- })
- default:
- resp.Header().Set("Content-Type", textMIMEType)
- resp.WriteHeader(http.StatusInternalServerError)
- io.WriteString(resp, errorText(err))
- }
- }
-
- func handleAPIError(resp http.ResponseWriter, req *http.Request, status int, err error) {
- var data struct {
- Error struct {
- Message string `json:"message"`
- } `json:"error"`
- }
- data.Error.Message = http.StatusText(status)
- resp.Header().Set("Content-Type", jsonMIMEType)
- resp.WriteHeader(status)
- json.NewEncoder(resp).Encode(&data)
- }
-
- type rootHandler []struct {
- prefix string
- h http.Handler
- }
-
- func (m rootHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
- var h http.Handler
- for _, ph := range m {
- if strings.HasPrefix(req.Host, ph.prefix) {
- h = ph.h
- break
- }
- }
-
- h.ServeHTTP(resp, req)
- }
-
- // otherDomainHandler redirects to another domain keeping the rest of the URL.
- type otherDomainHandler struct {
- scheme string
- targetDomain string
- }
-
- func (h otherDomainHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- u := *req.URL
- u.Scheme = h.scheme
- u.Host = h.targetDomain
- http.Redirect(w, req, u.String(), http.StatusFound)
- }
-
- func defaultBase(path string) string {
- p, err := build.Default.Import(path, "", build.FindOnly)
- if err != nil {
- return "."
- }
- return p.Dir
- }
-
- var (
- db *database.Database
- httpClient *http.Client
- statusImageHandlerPNG http.Handler
- statusImageHandlerSVG http.Handler
- gceLogger *GCELogger
- )
-
- func main() {
- doc.SetDefaultGOOS(viper.GetString(ConfigDefaultGOOS))
- httpClient = newHTTPClient()
-
- var (
- gceLogName string
- projID string
- )
-
- // TODO(stephenmw): merge into viper config infrastructure.
- if metadata.OnGCE() {
- acct, err := metadata.ProjectAttributeValue("ga-account")
- if err != nil {
- log.Printf("querying metadata for ga-account: %v", err)
- } else {
- gaAccount = acct
- }
-
- // Get the log name on GCE and setup context for creating a GCE log client.
- if name, err := metadata.ProjectAttributeValue("gce-log-name"); err != nil {
- log.Printf("querying metadata for gce-log-name: %v", err)
- } else {
- gceLogName = name
- if id, err := metadata.ProjectID(); err != nil {
- log.Printf("querying metadata for project ID: %v", err)
- } else {
- projID = id
- }
- }
- } else {
- gaAccount = os.Getenv("GA_ACCOUNT")
- }
-
- if err := parseHTMLTemplates([][]string{
- {"about.html", "common.html", "layout.html"},
- {"bot.html", "common.html", "layout.html"},
- {"cmd.html", "common.html", "layout.html"},
- {"dir.html", "common.html", "layout.html"},
- {"home.html", "common.html", "layout.html"},
- {"importers.html", "common.html", "layout.html"},
- {"importers_robot.html", "common.html", "layout.html"},
- {"imports.html", "common.html", "layout.html"},
- {"notfound.html", "common.html", "layout.html"},
- {"pkg.html", "common.html", "layout.html"},
- {"results.html", "common.html", "layout.html"},
- {"tools.html", "common.html", "layout.html"},
- {"std.html", "common.html", "layout.html"},
- {"subrepo.html", "common.html", "layout.html"},
- {"graph.html", "common.html"},
- }); err != nil {
- log.Fatal(err)
- }
-
- if err := parseTextTemplates([][]string{
- {"cmd.txt", "common.txt"},
- {"dir.txt", "common.txt"},
- {"home.txt", "common.txt"},
- {"notfound.txt", "common.txt"},
- {"pkg.txt", "common.txt"},
- {"results.txt", "common.txt"},
- }); err != nil {
- log.Fatal(err)
- }
-
- var err error
- db, err = database.New()
- if err != nil {
- log.Fatalf("Error opening database: %v", err)
- }
-
- go runBackgroundTasks()
-
- staticServer := httputil.StaticServer{
- Dir: viper.GetString(ConfigAssetsDir),
- MaxAge: time.Hour,
- MIMETypes: map[string]string{
- ".css": "text/css; charset=utf-8",
- ".js": "text/javascript; charset=utf-8",
- },
- }
- statusImageHandlerPNG = staticServer.FileHandler("status.png")
- statusImageHandlerSVG = staticServer.FileHandler("status.svg")
-
- apiMux := http.NewServeMux()
- apiMux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
- apiMux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
- apiMux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
- apiMux.Handle("/robots.txt", staticServer.FileHandler("apiRobots.txt"))
- apiMux.Handle("/search", apiHandler(serveAPISearch))
- apiMux.Handle("/packages", apiHandler(serveAPIPackages))
- apiMux.Handle("/importers/", apiHandler(serveAPIImporters))
- apiMux.Handle("/imports/", apiHandler(serveAPIImports))
- apiMux.Handle("/", apiHandler(serveAPIHome))
-
- mux := http.NewServeMux()
- mux.Handle("/-/site.js", staticServer.FilesHandler(
- "third_party/jquery.timeago.js",
- "site.js"))
- mux.Handle("/-/site.css", staticServer.FilesHandler("site.css"))
- mux.Handle("/-/bootstrap.min.css", staticServer.FilesHandler("bootstrap.min.css"))
- mux.Handle("/-/bootstrap.min.js", staticServer.FilesHandler("bootstrap.min.js"))
- mux.Handle("/-/jquery-2.0.3.min.js", staticServer.FilesHandler("jquery-2.0.3.min.js"))
- if viper.GetBool(ConfigSidebar) {
- mux.Handle("/-/sidebar.css", staticServer.FilesHandler("sidebar.css"))
- }
- mux.Handle("/-/", http.NotFoundHandler())
-
- mux.Handle("/-/about", handler(serveAbout))
- mux.Handle("/-/bot", handler(serveBot))
- mux.Handle("/-/go", handler(serveGoIndex))
- mux.Handle("/-/subrepo", handler(serveGoSubrepoIndex))
- mux.Handle("/-/refresh", handler(serveRefresh))
- mux.Handle("/-/admin/reindex", http.HandlerFunc(runReindex))
- mux.Handle("/-/admin/purgeindex", http.HandlerFunc(runPurgeIndex))
- mux.Handle("/about", http.RedirectHandler("/-/about", http.StatusMovedPermanently))
- mux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
- mux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
- mux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
- mux.Handle("/robots.txt", staticServer.FileHandler("robots.txt"))
- mux.Handle("/BingSiteAuth.xml", staticServer.FileHandler("BingSiteAuth.xml"))
- mux.Handle("/C", http.RedirectHandler("http://golang.org/doc/articles/c_go_cgo.html", http.StatusMovedPermanently))
- mux.Handle("/code.jquery.com/", http.NotFoundHandler())
- mux.Handle("/_ah/health", http.HandlerFunc(serveHealthCheck))
- mux.Handle("/_ah/", http.NotFoundHandler())
- mux.Handle("/", handler(serveHome))
-
- cacheBusters.Handler = mux
-
- var root http.Handler = rootHandler{
- {"api.", apiMux},
- {"talks.godoc.org", otherDomainHandler{"https", "go-talks.appspot.com"}},
- {"www.godoc.org", otherDomainHandler{"https", "godoc.org"}},
- {"", mux},
- }
- if gceLogName != "" {
- ctx := context.Background()
-
- logc, err := logging.NewClient(ctx, projID)
- if err != nil {
- log.Fatalf("Failed to create cloud logging client: %v", err)
- }
- logger := logc.Logger(gceLogName)
-
- if err := logc.Ping(ctx); err != nil {
- log.Fatalf("Failed to ping Google Cloud Logging: %v", err)
- }
-
- gceLogger = newGCELogger(logger)
- }
-
- http.Handle("/", root)
- appengine.Main()
- }
|