gddo-server: split main.go into two files

Pure code in motion.

Change-Id: Idd7e08bac2a1fdb9e2c79ca9a126becf30ea4235
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/285835
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/gddo-server/main.go b/gddo-server/main.go
index 57eac28..2fc0a69 100644
--- a/gddo-server/main.go
+++ b/gddo-server/main.go
@@ -8,1115 +8,15 @@
 package main
 
 import (
-	"bytes"
 	"context"
-	"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/logging"
-	"cloud.google.com/go/pubsub"
-	"cloud.google.com/go/trace"
-	"github.com/spf13/viper"
-
-	"github.com/golang/gddo/database"
 	"github.com/golang/gddo/doc"
-	"github.com/golang/gddo/gosrc"
-	"github.com/golang/gddo/httputil"
-	"github.com/golang/gddo/internal/health"
 )
 
-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 (s *server) getDoc(ctx context.Context, 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 := s.db.Get(ctx, 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 := s.crawlDoc(ctx, "web  ", path, pdoc, len(pkgs) > 0, nextCrawl)
-		c <- crawlResult{pdoc, err}
-	}()
-
-	timeout := s.v.GetDuration(ConfigGetTimeout)
-	if pdoc == nil {
-		timeout = s.v.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 (s *server) isRobot(req *http.Request) bool {
-	if robotPat.MatchString(req.Header.Get("User-Agent")) {
-		return true
-	}
-	host := httputil.StripPort(req.RemoteAddr)
-	n, err := s.db.IncrementCounter(host, 1)
-	if err != nil {
-		log.Printf("error incrementing counter for %s, %v", host, err)
-		return false
-	}
-	if n > s.v.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 (s *server) 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 s.v.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 (s *server) 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") {
-		s.statusSVG.ServeHTTP(resp, req)
-		return nil
-	}
-
-	if isView(req, "status.png") {
-		s.statusPNG.ServeHTTP(resp, req)
-		return nil
-	}
-
-	requestType := humanRequest
-	if s.isRobot(req) {
-		requestType = robotRequest
-	}
-
-	importPath := strings.TrimPrefix(req.URL.Path, "/")
-	pdoc, pkgs, err := s.getDoc(req.Context(), 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 := s.getDoc(req.Context(), 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 := s.db.Get(req.Context(), pkgs[0].Path)
-		if err != nil {
-			return err
-		}
-		pdoc = &doc.Package{
-			ProjectName: pdocChild.ProjectName,
-			ProjectRoot: pdocChild.ProjectRoot,
-			ProjectURL:  pdocChild.ProjectURL,
-			ImportPath:  importPath,
-		}
-	}
-
-	showPkgGoDevRedirectToast := userReturningFromPkgGoDev(req)
-
-	switch {
-	case isView(req, "imports"):
-		if pdoc.Name == "" {
-			return &httpError{status: http.StatusNotFound}
-		}
-		pkgs, err = s.db.Packages(pdoc.Imports)
-		if err != nil {
-			return err
-		}
-		return s.templates.execute(resp, "imports.html", http.StatusOK, nil, map[string]interface{}{
-			"flashMessages":             flashMessages,
-			"pkgs":                      pkgs,
-			"pdoc":                      newTDoc(s.v, pdoc),
-			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
-		})
-	case isView(req, "tools"):
-		proto := "http"
-		if req.Host == "godoc.org" {
-			proto = "https"
-		}
-		return s.templates.execute(resp, "tools.html", http.StatusOK, nil, map[string]interface{}{
-			"flashMessages":             flashMessages,
-			"uri":                       fmt.Sprintf("%s://%s/%s", proto, req.Host, importPath),
-			"pdoc":                      newTDoc(s.v, pdoc),
-			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
-		})
-	case isView(req, "importers"):
-		if pdoc.Name == "" {
-			return &httpError{status: http.StatusNotFound}
-		}
-		pkgs, err = s.db.Importers(importPath)
-		if err != nil {
-			return err
-		}
-		template := "importers.html"
-		if requestType == robotRequest {
-			// Hide back links from robots.
-			template = "importers_robot.html"
-		}
-		return s.templates.execute(resp, template, http.StatusOK, nil, map[string]interface{}{
-			"flashMessages":             flashMessages,
-			"pkgs":                      pkgs,
-			"pdoc":                      newTDoc(s.v, pdoc),
-			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
-		})
-	case isView(req, "import-graph"):
-		if requestType == robotRequest {
-			return &httpError{status: http.StatusForbidden}
-		}
-		if pdoc.Name == "" {
-			return &httpError{status: http.StatusNotFound}
-		}
-
-		// Throttle ?import-graph requests.
-		select {
-		case s.importGraphSem <- struct{}{}:
-		default:
-			return &httpError{status: http.StatusTooManyRequests}
-		}
-		defer func() { <-s.importGraphSem }()
-
-		hide := database.ShowAllDeps
-		switch req.Form.Get("hide") {
-		case "1":
-			hide = database.HideStandardDeps
-		case "2":
-			hide = database.HideStandardAll
-		}
-		pkgs, edges, err := s.db.ImportGraph(pdoc, hide)
-		if err != nil {
-			return err
-		}
-		b, err := renderGraph(pdoc, pkgs, edges)
-		if err != nil {
-			return err
-		}
-		return s.templates.execute(resp, "graph.html", http.StatusOK, nil, map[string]interface{}{
-			"flashMessages":             flashMessages,
-			"svg":                       template.HTML(b),
-			"pdoc":                      newTDoc(s.v, pdoc),
-			"hide":                      hide,
-			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
-		})
-	case isView(req, "play"):
-		u, err := s.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}
-	default:
-		importerCount := 0
-		if pdoc.Name != "" {
-			importerCount, err = s.db.ImporterCount(importPath)
-			if err != nil {
-				return err
-			}
-		}
-
-		etag := s.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 := s.db.IncrementPopularScore(pdoc.ImportPath); err != nil {
-				log.Printf("ERROR db.IncrementPopularScore(%s): %v", pdoc.ImportPath, err)
-			}
-		}
-		if s.gceLogger != nil {
-			s.gceLogger.LogEvent(resp, req, nil)
-		}
-
-		template := "dir"
-		switch {
-		case pdoc.IsCmd:
-			template = "cmd"
-		case pdoc.Name != "":
-			template = "pkg"
-		}
-		template += templateExt(req)
-
-		return s.templates.execute(resp, template, status, http.Header{"Etag": {etag}}, map[string]interface{}{
-			"flashMessages":             flashMessages,
-			"pkgs":                      pkgs,
-			"pdoc":                      newTDoc(s.v, pdoc),
-			"importerCount":             importerCount,
-			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
-		})
-	}
-}
-
-func (s *server) serveRefresh(resp http.ResponseWriter, req *http.Request) error {
-	importPath := req.Form.Get("path")
-	_, pkgs, _, err := s.db.Get(req.Context(), importPath)
-	if err != nil {
-		return err
-	}
-	c := make(chan error, 1)
-	go func() {
-		_, err := s.crawlDoc(req.Context(), "rfrsh", importPath, nil, len(pkgs) > 0, time.Time{})
-		c <- err
-	}()
-	select {
-	case err = <-c:
-	case <-time.After(s.v.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 (s *server) serveGoIndex(resp http.ResponseWriter, req *http.Request) error {
-	pkgs, err := s.db.GoIndex()
-	if err != nil {
-		return err
-	}
-	return s.templates.execute(resp, "std.html", http.StatusOK, nil, map[string]interface{}{
-		"pkgs": pkgs,
-	})
-}
-
-func (s *server) serveGoSubrepoIndex(resp http.ResponseWriter, req *http.Request) error {
-	pkgs, err := s.db.GoSubrepoIndex()
-	if err != nil {
-		return err
-	}
-	return s.templates.execute(resp, "subrepo.html", http.StatusOK, nil, map[string]interface{}{
-		"pkgs": pkgs,
-	})
-}
-
-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 (s *server) popular() ([]database.Package, error) {
-	const n = 25
-
-	pkgs, err := s.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 (s *server) serveHome(resp http.ResponseWriter, req *http.Request) error {
-	if req.URL.Path != "/" {
-		return s.servePackage(resp, req)
-	}
-
-	q := strings.TrimSpace(req.Form.Get("q"))
-	if q == "" {
-		pkgs, err := s.popular()
-		if err != nil {
-			return err
-		}
-
-		return s.templates.execute(resp, "home"+templateExt(req), http.StatusOK, nil,
-			map[string]interface{}{
-				"Popular": pkgs,
-
-				"showPkgGoDevRedirectToast": userReturningFromPkgGoDev(req),
-			})
-	}
-
-	if path, ok := isBrowseURL(q); ok {
-		q = path
-	}
-
-	if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
-		pdoc, pkgs, err := s.getDoc(req.Context(), 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
-		}
-	}
-
-	pkgs, err := s.db.Search(req.Context(), q)
-	if err != nil {
-		return err
-	}
-	if s.gceLogger != nil {
-		// Log up to top 10 packages we served upon a search.
-		logPkgs := pkgs
-		if len(pkgs) > 10 {
-			logPkgs = pkgs[:10]
-		}
-		s.gceLogger.LogEvent(resp, req, logPkgs)
-	}
-
-	return s.templates.execute(resp, "results"+templateExt(req), http.StatusOK, nil,
-		map[string]interface{}{
-			"q":    q,
-			"pkgs": pkgs,
-
-			"showPkgGoDevRedirectToast": userReturningFromPkgGoDev(req),
-		})
-}
-
-func (s *server) serveAbout(resp http.ResponseWriter, req *http.Request) error {
-	return s.templates.execute(resp, "about.html", http.StatusOK, nil,
-		map[string]interface{}{
-			"Host": req.Host,
-
-			"showPkgGoDevRedirectToast": userReturningFromPkgGoDev(req),
-		})
-}
-
-func (s *server) serveBot(resp http.ResponseWriter, req *http.Request) error {
-	return s.templates.execute(resp, "bot.html", http.StatusOK, nil, nil)
-}
-
-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 (s *server) 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 := s.getDoc(req.Context(), q, apiRequest)
-		if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
-			pdoc, _, err = s.getDoc(req.Context(), e.Redirect, robotRequest)
-		}
-		if err == nil && pdoc != nil {
-			pkgs = []database.Package{{Path: pdoc.ImportPath, Synopsis: pdoc.Synopsis}}
-		}
-	}
-
-	if pkgs == nil {
-		var err error
-		pkgs, err = s.db.Search(req.Context(), 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 (s *server) serveAPIPackages(resp http.ResponseWriter, req *http.Request) error {
-	pkgs, err := s.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 (s *server) serveAPIImporters(resp http.ResponseWriter, req *http.Request) error {
-	importPath := strings.TrimPrefix(req.URL.Path, "/importers/")
-	pkgs, err := s.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 (s *server) serveAPIImports(resp http.ResponseWriter, req *http.Request) error {
-	importPath := strings.TrimPrefix(req.URL.Path, "/imports/")
-	pdoc, _, err := s.getDoc(req.Context(), importPath, robotRequest)
-	if err != nil {
-		return err
-	}
-	if pdoc == nil || pdoc.Name == "" {
-		return &httpError{status: http.StatusNotFound}
-	}
-	imports, err := s.db.Packages(pdoc.Imports)
-	if err != nil {
-		return err
-	}
-	testImports, err := s.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}
-}
-
-type requestCleaner struct {
-	h                 http.Handler
-	trustProxyHeaders bool
-}
-
-func (rc requestCleaner) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-	req2 := new(http.Request)
-	*req2 = *req
-	if rc.trustProxyHeaders {
-		if s := req.Header.Get("X-Forwarded-For"); s != "" {
-			req2.RemoteAddr = s
-		}
-	}
-	req2.Body = http.MaxBytesReader(w, req.Body, 2048)
-	req2.ParseForm()
-	rc.h.ServeHTTP(w, req2)
-}
-
-type errorHandler struct {
-	fn    func(resp http.ResponseWriter, req *http.Request) error
-	errFn httputil.Error
-}
-
-func (eh errorHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
-	defer func() {
-		if rv := recover(); rv != nil {
-			err := errors.New("handler panic")
-			logError(req, err, rv)
-			eh.errFn(resp, req, http.StatusInternalServerError, err)
-		}
-	}()
-
-	rb := new(httputil.ResponseBuffer)
-	err := eh.fn(rb, req)
-	if err == nil {
-		rb.WriteTo(resp)
-	} else if e, ok := err.(*httpError); ok {
-		if e.status >= 500 {
-			logError(req, err, nil)
-		}
-		eh.errFn(resp, req, e.status, e.err)
-	} else if gosrc.IsNotFound(err) {
-		eh.errFn(resp, req, http.StatusNotFound, nil)
-	} else {
-		logError(req, err, nil)
-		eh.errFn(resp, req, http.StatusInternalServerError, err)
-	}
-}
-
-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 (s *server) handleError(resp http.ResponseWriter, req *http.Request, status int, err error) {
-	switch status {
-	case http.StatusNotFound:
-		s.templates.execute(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)
-}
-
-// httpsRedirectHandler redirects all requests with an X-Forwarded-Proto: http
-// handler to their https equivalent.
-type httpsRedirectHandler struct {
-	h http.Handler
-}
-
-func (h httpsRedirectHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
-	if req.Header.Get("X-Forwarded-Proto") == "http" {
-		u := *req.URL
-		u.Scheme = "https"
-		u.Host = req.Host
-		http.Redirect(resp, req, u.String(), http.StatusFound)
-		return
-	}
-	h.h.ServeHTTP(resp, req)
-}
-
-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
-}
-
-type server struct {
-	v           *viper.Viper
-	db          *database.Database
-	httpClient  *http.Client
-	gceLogger   *GCELogger
-	templates   templateMap
-	traceClient *trace.Client
-	crawlTopic  *pubsub.Topic
-
-	statusPNG http.Handler
-	statusSVG http.Handler
-
-	root rootHandler
-
-	// A semaphore to limit concurrent ?import-graph requests.
-	importGraphSem chan struct{}
-}
-
-func newServer(ctx context.Context, v *viper.Viper) (*server, error) {
-	s := &server{
-		v:              v,
-		httpClient:     newHTTPClient(v),
-		importGraphSem: make(chan struct{}, 10),
-	}
-
-	var err error
-	if proj := s.v.GetString(ConfigProject); proj != "" {
-		if s.traceClient, err = trace.NewClient(ctx, proj); err != nil {
-			return nil, err
-		}
-		sp, err := trace.NewLimitedSampler(s.v.GetFloat64(ConfigTraceSamplerFraction), s.v.GetFloat64(ConfigTraceSamplerMaxQPS))
-		if err != nil {
-			return nil, err
-		}
-		s.traceClient.SetSamplingPolicy(sp)
-
-		// This topic should be created in the cloud console.
-		ps, err := pubsub.NewClient(ctx, proj)
-		if err != nil {
-			return nil, err
-		}
-		s.crawlTopic = ps.Topic(ConfigCrawlPubSubTopic)
-	}
-
-	assets := v.GetString(ConfigAssetsDir)
-	staticServer := httputil.StaticServer{
-		Dir:    assets,
-		MaxAge: time.Hour,
-		MIMETypes: map[string]string{
-			".css": "text/css; charset=utf-8",
-			".js":  "text/javascript; charset=utf-8",
-		},
-	}
-	s.statusPNG = staticServer.FileHandler("status.png")
-	s.statusSVG = staticServer.FileHandler("status.svg")
-
-	apiHandler := func(f func(http.ResponseWriter, *http.Request) error) http.Handler {
-		return requestCleaner{
-			h: errorHandler{
-				fn:    f,
-				errFn: handleAPIError,
-			},
-			trustProxyHeaders: v.GetBool(ConfigTrustProxyHeaders),
-		}
-	}
-	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(s.serveAPISearch))
-	apiMux.Handle("/packages", apiHandler(s.serveAPIPackages))
-	apiMux.Handle("/importers/", apiHandler(s.serveAPIImporters))
-	apiMux.Handle("/imports/", apiHandler(s.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 s.v.GetBool(ConfigSidebar) {
-		mux.Handle("/-/sidebar.css", staticServer.FilesHandler("sidebar.css"))
-	}
-	mux.Handle("/-/", http.NotFoundHandler())
-
-	handler := func(f func(http.ResponseWriter, *http.Request) error) http.Handler {
-		return requestCleaner{
-			h: errorHandler{
-				fn:    f,
-				errFn: s.handleError,
-			},
-			trustProxyHeaders: v.GetBool(ConfigTrustProxyHeaders),
-		}
-	}
-
-	mux.Handle("/-/about", handler(pkgGoDevRedirectHandler(s.serveAbout)))
-	mux.Handle("/-/bot", handler(pkgGoDevRedirectHandler(s.serveBot)))
-	mux.Handle("/-/go", handler(pkgGoDevRedirectHandler(s.serveGoIndex)))
-	mux.Handle("/-/subrepo", handler(pkgGoDevRedirectHandler(s.serveGoSubrepoIndex)))
-	mux.Handle("/-/refresh", handler(s.serveRefresh))
-	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("/", handler(pkgGoDevRedirectHandler(s.serveHome)))
-
-	ahMux := http.NewServeMux()
-	ready := new(health.Handler)
-	ahMux.HandleFunc("/_ah/health", health.HandleLive)
-	ahMux.Handle("/_ah/ready", ready)
-
-	mainMux := http.NewServeMux()
-	mainMux.Handle("/_ah/", ahMux)
-	mainMux.Handle("/", s.traceClient.HTTPHandler(mux))
-
-	s.root = rootHandler{
-		{"api.", httpsRedirectHandler{s.traceClient.HTTPHandler(apiMux)}},
-		{"talks.godoc.org", otherDomainHandler{"https", "go-talks.appspot.com"}},
-		{"", httpsRedirectHandler{mainMux}},
-	}
-
-	cacheBusters := &httputil.CacheBusters{Handler: mux}
-	s.templates, err = parseTemplates(assets, cacheBusters, v)
-	if err != nil {
-		return nil, err
-	}
-	s.db, err = database.New(
-		v.GetString(ConfigDBServer),
-		v.GetDuration(ConfigDBIdleTimeout),
-		v.GetBool(ConfigDBLog),
-		v.GetString(ConfigGAERemoteAPI),
-	)
-	if err != nil {
-		return nil, fmt.Errorf("open database: %v", err)
-	}
-	ready.Add(s.db)
-	if gceLogName := v.GetString(ConfigGCELogName); gceLogName != "" {
-		logc, err := logging.NewClient(ctx, v.GetString(ConfigProject))
-		if err != nil {
-			return nil, fmt.Errorf("create cloud logging client: %v", err)
-		}
-		logger := logc.Logger(gceLogName)
-		if err := logc.Ping(ctx); err != nil {
-			return nil, fmt.Errorf("pinging cloud logging: %v", err)
-		}
-		s.gceLogger = newGCELogger(logger)
-	}
-	return s, nil
-}
-
-type responseWriter struct {
-	http.ResponseWriter
-	status int
-}
-
-func (rw *responseWriter) WriteHeader(code int) {
-	rw.status = code
-	rw.ResponseWriter.WriteHeader(code)
-}
-
-func translateStatus(code int) int {
-	if code == 0 {
-		return http.StatusOK
-	}
-	return code
-}
-
-func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	start := time.Now()
-	s.logRequestStart(r)
-	w2 := &responseWriter{ResponseWriter: w}
-	s.root.ServeHTTP(w2, r)
-	latency := time.Since(start)
-	s.logRequestEnd(r, latency)
-	if f, ok := w.(http.Flusher); ok {
-		f.Flush()
-	}
-	s.teeRequestToPkgGoDev(r, latency, translateStatus(w2.status))
-}
-
-func (s *server) logRequestStart(req *http.Request) {
-	if s.gceLogger == nil {
-		return
-	}
-	s.gceLogger.Log(logging.Entry{
-		HTTPRequest: &logging.HTTPRequest{Request: req},
-		Payload:     fmt.Sprintf("%s request start", req.Host),
-		Severity:    logging.Info,
-	})
-}
-
-func (s *server) logRequestEnd(req *http.Request, latency time.Duration) {
-	if s.gceLogger == nil {
-		return
-	}
-	s.gceLogger.Log(logging.Entry{
-		HTTPRequest: &logging.HTTPRequest{
-			Request: req,
-			Latency: latency,
-		},
-		Payload:  fmt.Sprintf("%s request end", req.Host),
-		Severity: logging.Info,
-	})
-}
-
-func (s *server) teeRequestToPkgGoDev(r *http.Request, latency time.Duration, status int) {
-	if shouldRedirectRequest(r) {
-		log.Printf("shouldRedirectToPkgGoDev(%q, %q)= true: not teeing request because it is redirected to pkg.go.dev", r.URL.Host, r.URL.Path)
-		return
-	}
-	if !shouldTeeRequest(r.URL.Path) {
-		log.Printf("s.teeRequestToPkgGoDev: shouldTeeRequest(%q): not teeing request", r.URL.Path)
-		return
-	}
-	val := os.Getenv("GDDO_TEE_REQUESTS_TO_PKGGODEV")
-	if strings.ToLower(val) != "true" {
-		log.Printf("s.teeRequestToPkgGoDev: not sending requests to pkg.go.dev (GDDO_TEE_REQUESTS_TO_PKGGODEV = %q)", val)
-		return
-	}
-
-	log.Printf("s.teeRequestToPkgGoDev: teeRequestToPkgGoDev for %q (%d)", r.URL.Path, status)
-	gddoEvent, pkggodevEvent := teeRequestToPkgGoDev(r, latency, s.isRobot(r), status)
-	payload := map[string]interface{}{
-		"godoc.org":  gddoEvent,
-		"pkg.go.dev": pkggodevEvent,
-	}
-	log.Printf("s.teeRequestToPkgGoDev: logging gddoEvent and pkggodevEvent: %q", r.URL.Path)
-	if s.gceLogger == nil {
-		log.Printf("no s.gceLogger: %q", r.URL.Path)
-		for k, v := range payload {
-			log.Printf("%q", k)
-			log.Printf("%+v", v)
-		}
-		return
-	}
-	log.Printf("s.teeRequestToPkgGoDev: s.gceLogger.Log:%q", r.URL.Path)
-	s.gceLogger.Log(logging.Entry{
-		HTTPRequest: &logging.HTTPRequest{
-			Request: r,
-			Latency: latency,
-			Status:  status,
-		},
-		Payload:  payload,
-		Severity: logging.Info,
-	})
-	return
-}
-
 func main() {
 	ctx := context.Background()
 	v, err := loadConfig(ctx, os.Args)
diff --git a/gddo-server/server.go b/gddo-server/server.go
new file mode 100644
index 0000000..07ebf1f
--- /dev/null
+++ b/gddo-server/server.go
@@ -0,0 +1,1117 @@
+// Copyright 2021 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"
+	"context"
+	"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/logging"
+	"cloud.google.com/go/pubsub"
+	"cloud.google.com/go/trace"
+	"github.com/golang/gddo/database"
+	"github.com/golang/gddo/doc"
+	"github.com/golang/gddo/gosrc"
+	"github.com/golang/gddo/httputil"
+	"github.com/golang/gddo/internal/health"
+	"github.com/spf13/viper"
+)
+
+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 (s *server) getDoc(ctx context.Context, 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 := s.db.Get(ctx, 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 := s.crawlDoc(ctx, "web  ", path, pdoc, len(pkgs) > 0, nextCrawl)
+		c <- crawlResult{pdoc, err}
+	}()
+
+	timeout := s.v.GetDuration(ConfigGetTimeout)
+	if pdoc == nil {
+		timeout = s.v.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 (s *server) isRobot(req *http.Request) bool {
+	if robotPat.MatchString(req.Header.Get("User-Agent")) {
+		return true
+	}
+	host := httputil.StripPort(req.RemoteAddr)
+	n, err := s.db.IncrementCounter(host, 1)
+	if err != nil {
+		log.Printf("error incrementing counter for %s, %v", host, err)
+		return false
+	}
+	if n > s.v.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 (s *server) 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 s.v.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 (s *server) 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") {
+		s.statusSVG.ServeHTTP(resp, req)
+		return nil
+	}
+
+	if isView(req, "status.png") {
+		s.statusPNG.ServeHTTP(resp, req)
+		return nil
+	}
+
+	requestType := humanRequest
+	if s.isRobot(req) {
+		requestType = robotRequest
+	}
+
+	importPath := strings.TrimPrefix(req.URL.Path, "/")
+	pdoc, pkgs, err := s.getDoc(req.Context(), 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 := s.getDoc(req.Context(), 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 := s.db.Get(req.Context(), pkgs[0].Path)
+		if err != nil {
+			return err
+		}
+		pdoc = &doc.Package{
+			ProjectName: pdocChild.ProjectName,
+			ProjectRoot: pdocChild.ProjectRoot,
+			ProjectURL:  pdocChild.ProjectURL,
+			ImportPath:  importPath,
+		}
+	}
+
+	showPkgGoDevRedirectToast := userReturningFromPkgGoDev(req)
+
+	switch {
+	case isView(req, "imports"):
+		if pdoc.Name == "" {
+			return &httpError{status: http.StatusNotFound}
+		}
+		pkgs, err = s.db.Packages(pdoc.Imports)
+		if err != nil {
+			return err
+		}
+		return s.templates.execute(resp, "imports.html", http.StatusOK, nil, map[string]interface{}{
+			"flashMessages":             flashMessages,
+			"pkgs":                      pkgs,
+			"pdoc":                      newTDoc(s.v, pdoc),
+			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
+		})
+	case isView(req, "tools"):
+		proto := "http"
+		if req.Host == "godoc.org" {
+			proto = "https"
+		}
+		return s.templates.execute(resp, "tools.html", http.StatusOK, nil, map[string]interface{}{
+			"flashMessages":             flashMessages,
+			"uri":                       fmt.Sprintf("%s://%s/%s", proto, req.Host, importPath),
+			"pdoc":                      newTDoc(s.v, pdoc),
+			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
+		})
+	case isView(req, "importers"):
+		if pdoc.Name == "" {
+			return &httpError{status: http.StatusNotFound}
+		}
+		pkgs, err = s.db.Importers(importPath)
+		if err != nil {
+			return err
+		}
+		template := "importers.html"
+		if requestType == robotRequest {
+			// Hide back links from robots.
+			template = "importers_robot.html"
+		}
+		return s.templates.execute(resp, template, http.StatusOK, nil, map[string]interface{}{
+			"flashMessages":             flashMessages,
+			"pkgs":                      pkgs,
+			"pdoc":                      newTDoc(s.v, pdoc),
+			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
+		})
+	case isView(req, "import-graph"):
+		if requestType == robotRequest {
+			return &httpError{status: http.StatusForbidden}
+		}
+		if pdoc.Name == "" {
+			return &httpError{status: http.StatusNotFound}
+		}
+
+		// Throttle ?import-graph requests.
+		select {
+		case s.importGraphSem <- struct{}{}:
+		default:
+			return &httpError{status: http.StatusTooManyRequests}
+		}
+		defer func() { <-s.importGraphSem }()
+
+		hide := database.ShowAllDeps
+		switch req.Form.Get("hide") {
+		case "1":
+			hide = database.HideStandardDeps
+		case "2":
+			hide = database.HideStandardAll
+		}
+		pkgs, edges, err := s.db.ImportGraph(pdoc, hide)
+		if err != nil {
+			return err
+		}
+		b, err := renderGraph(pdoc, pkgs, edges)
+		if err != nil {
+			return err
+		}
+		return s.templates.execute(resp, "graph.html", http.StatusOK, nil, map[string]interface{}{
+			"flashMessages":             flashMessages,
+			"svg":                       template.HTML(b),
+			"pdoc":                      newTDoc(s.v, pdoc),
+			"hide":                      hide,
+			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
+		})
+	case isView(req, "play"):
+		u, err := s.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}
+	default:
+		importerCount := 0
+		if pdoc.Name != "" {
+			importerCount, err = s.db.ImporterCount(importPath)
+			if err != nil {
+				return err
+			}
+		}
+
+		etag := s.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 := s.db.IncrementPopularScore(pdoc.ImportPath); err != nil {
+				log.Printf("ERROR db.IncrementPopularScore(%s): %v", pdoc.ImportPath, err)
+			}
+		}
+		if s.gceLogger != nil {
+			s.gceLogger.LogEvent(resp, req, nil)
+		}
+
+		template := "dir"
+		switch {
+		case pdoc.IsCmd:
+			template = "cmd"
+		case pdoc.Name != "":
+			template = "pkg"
+		}
+		template += templateExt(req)
+
+		return s.templates.execute(resp, template, status, http.Header{"Etag": {etag}}, map[string]interface{}{
+			"flashMessages":             flashMessages,
+			"pkgs":                      pkgs,
+			"pdoc":                      newTDoc(s.v, pdoc),
+			"importerCount":             importerCount,
+			"showPkgGoDevRedirectToast": showPkgGoDevRedirectToast,
+		})
+	}
+}
+
+func (s *server) serveRefresh(resp http.ResponseWriter, req *http.Request) error {
+	importPath := req.Form.Get("path")
+	_, pkgs, _, err := s.db.Get(req.Context(), importPath)
+	if err != nil {
+		return err
+	}
+	c := make(chan error, 1)
+	go func() {
+		_, err := s.crawlDoc(req.Context(), "rfrsh", importPath, nil, len(pkgs) > 0, time.Time{})
+		c <- err
+	}()
+	select {
+	case err = <-c:
+	case <-time.After(s.v.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 (s *server) serveGoIndex(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := s.db.GoIndex()
+	if err != nil {
+		return err
+	}
+	return s.templates.execute(resp, "std.html", http.StatusOK, nil, map[string]interface{}{
+		"pkgs": pkgs,
+	})
+}
+
+func (s *server) serveGoSubrepoIndex(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := s.db.GoSubrepoIndex()
+	if err != nil {
+		return err
+	}
+	return s.templates.execute(resp, "subrepo.html", http.StatusOK, nil, map[string]interface{}{
+		"pkgs": pkgs,
+	})
+}
+
+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 (s *server) popular() ([]database.Package, error) {
+	const n = 25
+
+	pkgs, err := s.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 (s *server) serveHome(resp http.ResponseWriter, req *http.Request) error {
+	if req.URL.Path != "/" {
+		return s.servePackage(resp, req)
+	}
+
+	q := strings.TrimSpace(req.Form.Get("q"))
+	if q == "" {
+		pkgs, err := s.popular()
+		if err != nil {
+			return err
+		}
+
+		return s.templates.execute(resp, "home"+templateExt(req), http.StatusOK, nil,
+			map[string]interface{}{
+				"Popular": pkgs,
+
+				"showPkgGoDevRedirectToast": userReturningFromPkgGoDev(req),
+			})
+	}
+
+	if path, ok := isBrowseURL(q); ok {
+		q = path
+	}
+
+	if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
+		pdoc, pkgs, err := s.getDoc(req.Context(), 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
+		}
+	}
+
+	pkgs, err := s.db.Search(req.Context(), q)
+	if err != nil {
+		return err
+	}
+	if s.gceLogger != nil {
+		// Log up to top 10 packages we served upon a search.
+		logPkgs := pkgs
+		if len(pkgs) > 10 {
+			logPkgs = pkgs[:10]
+		}
+		s.gceLogger.LogEvent(resp, req, logPkgs)
+	}
+
+	return s.templates.execute(resp, "results"+templateExt(req), http.StatusOK, nil,
+		map[string]interface{}{
+			"q":    q,
+			"pkgs": pkgs,
+
+			"showPkgGoDevRedirectToast": userReturningFromPkgGoDev(req),
+		})
+}
+
+func (s *server) serveAbout(resp http.ResponseWriter, req *http.Request) error {
+	return s.templates.execute(resp, "about.html", http.StatusOK, nil,
+		map[string]interface{}{
+			"Host": req.Host,
+
+			"showPkgGoDevRedirectToast": userReturningFromPkgGoDev(req),
+		})
+}
+
+func (s *server) serveBot(resp http.ResponseWriter, req *http.Request) error {
+	return s.templates.execute(resp, "bot.html", http.StatusOK, nil, nil)
+}
+
+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 (s *server) 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 := s.getDoc(req.Context(), q, apiRequest)
+		if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
+			pdoc, _, err = s.getDoc(req.Context(), e.Redirect, robotRequest)
+		}
+		if err == nil && pdoc != nil {
+			pkgs = []database.Package{{Path: pdoc.ImportPath, Synopsis: pdoc.Synopsis}}
+		}
+	}
+
+	if pkgs == nil {
+		var err error
+		pkgs, err = s.db.Search(req.Context(), 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 (s *server) serveAPIPackages(resp http.ResponseWriter, req *http.Request) error {
+	pkgs, err := s.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 (s *server) serveAPIImporters(resp http.ResponseWriter, req *http.Request) error {
+	importPath := strings.TrimPrefix(req.URL.Path, "/importers/")
+	pkgs, err := s.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 (s *server) serveAPIImports(resp http.ResponseWriter, req *http.Request) error {
+	importPath := strings.TrimPrefix(req.URL.Path, "/imports/")
+	pdoc, _, err := s.getDoc(req.Context(), importPath, robotRequest)
+	if err != nil {
+		return err
+	}
+	if pdoc == nil || pdoc.Name == "" {
+		return &httpError{status: http.StatusNotFound}
+	}
+	imports, err := s.db.Packages(pdoc.Imports)
+	if err != nil {
+		return err
+	}
+	testImports, err := s.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}
+}
+
+type requestCleaner struct {
+	h                 http.Handler
+	trustProxyHeaders bool
+}
+
+func (rc requestCleaner) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	req2 := new(http.Request)
+	*req2 = *req
+	if rc.trustProxyHeaders {
+		if s := req.Header.Get("X-Forwarded-For"); s != "" {
+			req2.RemoteAddr = s
+		}
+	}
+	req2.Body = http.MaxBytesReader(w, req.Body, 2048)
+	req2.ParseForm()
+	rc.h.ServeHTTP(w, req2)
+}
+
+type errorHandler struct {
+	fn    func(resp http.ResponseWriter, req *http.Request) error
+	errFn httputil.Error
+}
+
+func (eh errorHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	defer func() {
+		if rv := recover(); rv != nil {
+			err := errors.New("handler panic")
+			logError(req, err, rv)
+			eh.errFn(resp, req, http.StatusInternalServerError, err)
+		}
+	}()
+
+	rb := new(httputil.ResponseBuffer)
+	err := eh.fn(rb, req)
+	if err == nil {
+		rb.WriteTo(resp)
+	} else if e, ok := err.(*httpError); ok {
+		if e.status >= 500 {
+			logError(req, err, nil)
+		}
+		eh.errFn(resp, req, e.status, e.err)
+	} else if gosrc.IsNotFound(err) {
+		eh.errFn(resp, req, http.StatusNotFound, nil)
+	} else {
+		logError(req, err, nil)
+		eh.errFn(resp, req, http.StatusInternalServerError, err)
+	}
+}
+
+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 (s *server) handleError(resp http.ResponseWriter, req *http.Request, status int, err error) {
+	switch status {
+	case http.StatusNotFound:
+		s.templates.execute(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)
+}
+
+// httpsRedirectHandler redirects all requests with an X-Forwarded-Proto: http
+// handler to their https equivalent.
+type httpsRedirectHandler struct {
+	h http.Handler
+}
+
+func (h httpsRedirectHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	if req.Header.Get("X-Forwarded-Proto") == "http" {
+		u := *req.URL
+		u.Scheme = "https"
+		u.Host = req.Host
+		http.Redirect(resp, req, u.String(), http.StatusFound)
+		return
+	}
+	h.h.ServeHTTP(resp, req)
+}
+
+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
+}
+
+type server struct {
+	v           *viper.Viper
+	db          *database.Database
+	httpClient  *http.Client
+	gceLogger   *GCELogger
+	templates   templateMap
+	traceClient *trace.Client
+	crawlTopic  *pubsub.Topic
+
+	statusPNG http.Handler
+	statusSVG http.Handler
+
+	root rootHandler
+
+	// A semaphore to limit concurrent ?import-graph requests.
+	importGraphSem chan struct{}
+}
+
+func newServer(ctx context.Context, v *viper.Viper) (*server, error) {
+	s := &server{
+		v:              v,
+		httpClient:     newHTTPClient(v),
+		importGraphSem: make(chan struct{}, 10),
+	}
+
+	var err error
+	if proj := s.v.GetString(ConfigProject); proj != "" {
+		if s.traceClient, err = trace.NewClient(ctx, proj); err != nil {
+			return nil, err
+		}
+		sp, err := trace.NewLimitedSampler(s.v.GetFloat64(ConfigTraceSamplerFraction), s.v.GetFloat64(ConfigTraceSamplerMaxQPS))
+		if err != nil {
+			return nil, err
+		}
+		s.traceClient.SetSamplingPolicy(sp)
+
+		// This topic should be created in the cloud console.
+		ps, err := pubsub.NewClient(ctx, proj)
+		if err != nil {
+			return nil, err
+		}
+		s.crawlTopic = ps.Topic(ConfigCrawlPubSubTopic)
+	}
+
+	assets := v.GetString(ConfigAssetsDir)
+	staticServer := httputil.StaticServer{
+		Dir:    assets,
+		MaxAge: time.Hour,
+		MIMETypes: map[string]string{
+			".css": "text/css; charset=utf-8",
+			".js":  "text/javascript; charset=utf-8",
+		},
+	}
+	s.statusPNG = staticServer.FileHandler("status.png")
+	s.statusSVG = staticServer.FileHandler("status.svg")
+
+	apiHandler := func(f func(http.ResponseWriter, *http.Request) error) http.Handler {
+		return requestCleaner{
+			h: errorHandler{
+				fn:    f,
+				errFn: handleAPIError,
+			},
+			trustProxyHeaders: v.GetBool(ConfigTrustProxyHeaders),
+		}
+	}
+	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(s.serveAPISearch))
+	apiMux.Handle("/packages", apiHandler(s.serveAPIPackages))
+	apiMux.Handle("/importers/", apiHandler(s.serveAPIImporters))
+	apiMux.Handle("/imports/", apiHandler(s.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 s.v.GetBool(ConfigSidebar) {
+		mux.Handle("/-/sidebar.css", staticServer.FilesHandler("sidebar.css"))
+	}
+	mux.Handle("/-/", http.NotFoundHandler())
+
+	handler := func(f func(http.ResponseWriter, *http.Request) error) http.Handler {
+		return requestCleaner{
+			h: errorHandler{
+				fn:    f,
+				errFn: s.handleError,
+			},
+			trustProxyHeaders: v.GetBool(ConfigTrustProxyHeaders),
+		}
+	}
+
+	mux.Handle("/-/about", handler(pkgGoDevRedirectHandler(s.serveAbout)))
+	mux.Handle("/-/bot", handler(pkgGoDevRedirectHandler(s.serveBot)))
+	mux.Handle("/-/go", handler(pkgGoDevRedirectHandler(s.serveGoIndex)))
+	mux.Handle("/-/subrepo", handler(pkgGoDevRedirectHandler(s.serveGoSubrepoIndex)))
+	mux.Handle("/-/refresh", handler(s.serveRefresh))
+	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("/", handler(pkgGoDevRedirectHandler(s.serveHome)))
+
+	ahMux := http.NewServeMux()
+	ready := new(health.Handler)
+	ahMux.HandleFunc("/_ah/health", health.HandleLive)
+	ahMux.Handle("/_ah/ready", ready)
+
+	mainMux := http.NewServeMux()
+	mainMux.Handle("/_ah/", ahMux)
+	mainMux.Handle("/", s.traceClient.HTTPHandler(mux))
+
+	s.root = rootHandler{
+		{"api.", httpsRedirectHandler{s.traceClient.HTTPHandler(apiMux)}},
+		{"talks.godoc.org", otherDomainHandler{"https", "go-talks.appspot.com"}},
+		{"", httpsRedirectHandler{mainMux}},
+	}
+
+	cacheBusters := &httputil.CacheBusters{Handler: mux}
+	s.templates, err = parseTemplates(assets, cacheBusters, v)
+	if err != nil {
+		return nil, err
+	}
+	s.db, err = database.New(
+		v.GetString(ConfigDBServer),
+		v.GetDuration(ConfigDBIdleTimeout),
+		v.GetBool(ConfigDBLog),
+		v.GetString(ConfigGAERemoteAPI),
+	)
+	if err != nil {
+		return nil, fmt.Errorf("open database: %v", err)
+	}
+	ready.Add(s.db)
+	if gceLogName := v.GetString(ConfigGCELogName); gceLogName != "" {
+		logc, err := logging.NewClient(ctx, v.GetString(ConfigProject))
+		if err != nil {
+			return nil, fmt.Errorf("create cloud logging client: %v", err)
+		}
+		logger := logc.Logger(gceLogName)
+		if err := logc.Ping(ctx); err != nil {
+			return nil, fmt.Errorf("pinging cloud logging: %v", err)
+		}
+		s.gceLogger = newGCELogger(logger)
+	}
+	return s, nil
+}
+
+type responseWriter struct {
+	http.ResponseWriter
+	status int
+}
+
+func (rw *responseWriter) WriteHeader(code int) {
+	rw.status = code
+	rw.ResponseWriter.WriteHeader(code)
+}
+
+func translateStatus(code int) int {
+	if code == 0 {
+		return http.StatusOK
+	}
+	return code
+}
+
+func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	start := time.Now()
+	s.logRequestStart(r)
+	w2 := &responseWriter{ResponseWriter: w}
+	s.root.ServeHTTP(w2, r)
+	latency := time.Since(start)
+	s.logRequestEnd(r, latency)
+	if f, ok := w.(http.Flusher); ok {
+		f.Flush()
+	}
+	s.teeRequestToPkgGoDev(r, latency, translateStatus(w2.status))
+}
+
+func (s *server) logRequestStart(req *http.Request) {
+	if s.gceLogger == nil {
+		return
+	}
+	s.gceLogger.Log(logging.Entry{
+		HTTPRequest: &logging.HTTPRequest{Request: req},
+		Payload:     fmt.Sprintf("%s request start", req.Host),
+		Severity:    logging.Info,
+	})
+}
+
+func (s *server) logRequestEnd(req *http.Request, latency time.Duration) {
+	if s.gceLogger == nil {
+		return
+	}
+	s.gceLogger.Log(logging.Entry{
+		HTTPRequest: &logging.HTTPRequest{
+			Request: req,
+			Latency: latency,
+		},
+		Payload:  fmt.Sprintf("%s request end", req.Host),
+		Severity: logging.Info,
+	})
+}
+
+func (s *server) teeRequestToPkgGoDev(r *http.Request, latency time.Duration, status int) {
+	if shouldRedirectRequest(r) {
+		log.Printf("shouldRedirectToPkgGoDev(%q, %q)= true: not teeing request because it is redirected to pkg.go.dev", r.URL.Host, r.URL.Path)
+		return
+	}
+	if !shouldTeeRequest(r.URL.Path) {
+		log.Printf("s.teeRequestToPkgGoDev: shouldTeeRequest(%q): not teeing request", r.URL.Path)
+		return
+	}
+	val := os.Getenv("GDDO_TEE_REQUESTS_TO_PKGGODEV")
+	if strings.ToLower(val) != "true" {
+		log.Printf("s.teeRequestToPkgGoDev: not sending requests to pkg.go.dev (GDDO_TEE_REQUESTS_TO_PKGGODEV = %q)", val)
+		return
+	}
+
+	log.Printf("s.teeRequestToPkgGoDev: teeRequestToPkgGoDev for %q (%d)", r.URL.Path, status)
+	gddoEvent, pkggodevEvent := teeRequestToPkgGoDev(r, latency, s.isRobot(r), status)
+	payload := map[string]interface{}{
+		"godoc.org":  gddoEvent,
+		"pkg.go.dev": pkggodevEvent,
+	}
+	log.Printf("s.teeRequestToPkgGoDev: logging gddoEvent and pkggodevEvent: %q", r.URL.Path)
+	if s.gceLogger == nil {
+		log.Printf("no s.gceLogger: %q", r.URL.Path)
+		for k, v := range payload {
+			log.Printf("%q", k)
+			log.Printf("%+v", v)
+		}
+		return
+	}
+	log.Printf("s.teeRequestToPkgGoDev: s.gceLogger.Log:%q", r.URL.Path)
+	s.gceLogger.Log(logging.Entry{
+		HTTPRequest: &logging.HTTPRequest{
+			Request: r,
+			Latency: latency,
+			Status:  status,
+		},
+		Payload:  payload,
+		Severity: logging.Info,
+	})
+	return
+}