Copyright 2019 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.
Package frontend provides functionality for running the pkg.go.dev site.
package frontend

import (
	
	
	
	
	
	
	
	
	

	
	
	
	
	
	
	
	
	
	
	
	
	
	
)
Server can be installed to serve the go discovery frontend.
getDataSource should never be called from a handler. It is called only in Server.errorHandler.
ServerConfig contains everything needed by a Server.
NewServer creates a new Server for the given database and template directory.
func ( ServerConfig) ( *Server,  error) {
	defer derrors.Wrap(&, "NewServer(...)")
	 := template.TrustedSourceJoin(.StaticPath)
	,  := parsePageTemplates()
	if  != nil {
		return nil, fmt.Errorf("error parsing templates: %v", )
	}
	 := template.TrustedSourceJoin(, template.TrustedSourceFromConstant("html"),
		template.TrustedSourceFromConstant("doc"))
	dochtml.LoadTemplates()
	 := &Server{
		getDataSource:        .DataSourceGetter,
		queue:                .Queue,
		cmplClient:           .CompletionClient,
		staticPath:           .StaticPath,
		thirdPartyPath:       .ThirdPartyPath,
		templateDir:          ,
		devMode:              .DevMode,
		templates:            ,
		taskIDChangeInterval: .TaskIDChangeInterval,
		appVersionLabel:      .AppVersionLabel,
		googleTagManagerID:   .GoogleTagManagerID,
		serveStats:           .ServeStats,
		reportingClient:      .ReportingClient,
	}
	,  := .renderErrorPage(context.Background(), http.StatusInternalServerError, "error.tmpl", nil)
	if  != nil {
		return nil, fmt.Errorf("s.renderErrorPage(http.StatusInternalServerError, nil): %v", )
	}
	.errorPage = 
	return , nil
}
Install registers server routes using the given handler registration func. authValues is the set of values that can be set on authHeader to bypass the cache.
func ( *Server) ( func(string, http.Handler),  *redis.Client,  []string) {
	var (
		 http.Handler = .errorHandler(.serveDetails)
		  http.Handler = .errorHandler(.serveFetch)
		 http.Handler = .errorHandler(.serveSearch)
	)
	if  != nil {
		 = middleware.Cache("details", , detailsTTL, )()
		 = middleware.Cache("search", , middleware.TTL(defaultTTL), )()
Each AppEngine instance is created in response to a start request, which is an empty HTTP GET request to /_ah/start when scaling is set to manual or basic, and /_ah/warmup when scaling is automatic and min_instances is set. AppEngine sends this request to bring an instance into existence. See details for /_ah/start at https://cloud.google.com/appengine/docs/standard/go/how-instances-are-managed#startup and for /_ah/warmup at https://cloud.google.com/appengine/docs/standard/go/configuring-warmup-requests.
	("/_ah/", http.HandlerFunc(func( http.ResponseWriter,  *http.Request) {
		log.Infof(.Context(), "Request made to %q", .URL.Path)
	}))
	("/static/", .staticHandler())
	("/third_party/", http.StripPrefix("/third_party", http.FileServer(http.Dir(.thirdPartyPath))))
	("/favicon.ico", http.HandlerFunc(func( http.ResponseWriter,  *http.Request) {
		http.ServeFile(, , fmt.Sprintf("%s/img/favicon.ico", http.Dir(.staticPath.String())))
	}))
	("/mod/", http.HandlerFunc(.handleModuleDetailsRedirect))
	("/pkg/", http.HandlerFunc(.handlePackageDetailsRedirect))
	("/fetch/", )
	("/play/compile", http.HandlerFunc(.proxyPlayground))
	("/play/fmt", http.HandlerFunc(.handleFmt))
	("/play/share", http.HandlerFunc(.proxyPlayground))
	("/search", )
	("/search-help", .staticPageHandler("search_help.tmpl", "Search Help"))
	("/license-policy", .licensePolicyHandler())
	("/about", http.RedirectHandler("https://go.dev/about", http.StatusFound))
	("/badge/", http.HandlerFunc(.badgeHandler))
	("/styleguide", http.HandlerFunc(.errorHandler(.serveStyleGuide)))
Package "C" is a special case: redirect to /cmd/cgo. (This is what golang.org/C does.)
		http.Redirect(, , "/cmd/cgo", http.StatusMovedPermanently)
	}))
	("/", )
	if .serveStats {
		("/detail-stats/",
			middleware.Stats()(http.StripPrefix("/detail-stats", .errorHandler(.serveDetails))))
	}
	("/robots.txt", http.HandlerFunc(func( http.ResponseWriter,  *http.Request) {
		.Header().Set("Content-Type", "text/plain; charset=utf-8")
		http.ServeContent(, , "", time.Time{}, strings.NewReader(`User-agent: *
Disallow: /search?*
Disallow: /fetch/*
`))
	}))
}

defaultTTL is used when details tab contents are subject to change, or when there is a problem confirming that the details can be permanently cached.
shortTTL is used for volatile content, such as the latest version of a package or module.
longTTL is used when details content is essentially static.
tinyTTL is used to cache crawled pages.
	tinyTTL = 1 * time.Minute
)

var crawlers = []string{
	"+http://www.google.com/bot.html",
	"+http://www.bing.com/bingbot.htm",
	"+http://ahrefs.com/robot",
}
detailsTTL assigns the cache TTL for package detail requests.
func ( *http.Request) time.Duration {
	 := .Header.Get("User-Agent")
	for ,  := range crawlers {
		if strings.Contains(, ) {
			return tinyTTL
		}
	}
	return detailsTTLForPath(.Context(), .URL.Path, .FormValue("tab"))
}

func ( context.Context, ,  string) time.Duration {
	if  == "/" {
		return defaultTTL
	}
	,  := parseDetailsURLPath()
	if  != nil {
		log.Errorf(, "falling back to default TTL: %v", )
		return defaultTTL
	}
	if .requestedVersion == internal.LatestVersion {
		return shortTTL
	}
	if  == "importedby" ||  == "versions" {
		return defaultTTL
	}
	return longTTL
}
TagRoute categorizes incoming requests to the frontend for use in monitoring.
func ( string,  *http.Request) string {
	 := strings.Trim(, "/")
Verify that the tab value actually exists, otherwise this is unsanitized input and could result in unbounded cardinality in our metrics.
		if ,  := unitTabLookup[];  {
			if  != "" {
				 += "-"
			}
			 += 
		}
	}
	return 
}
staticPageHandler handles requests to a template that contains no dynamic content.
func ( *Server) (,  string) http.HandlerFunc {
	return func( http.ResponseWriter,  *http.Request) {
		.servePage(.Context(), , , .newBasePage(, ))
	}
}
basePage contains fields shared by all pages when rendering templates.
HTMLTitle is the value to use in the page’s <title> tag.
MetaDescription is the html used for rendering the <meta name="Description"> tag.
Query is the current search query (if applicable).
Experiments contains the experiments currently active.
DevMode indicates whether the server is running in development mode.
AppVersionLabel contains the current version of the app.
GoogleTagManagerID is the ID used to load Google Tag Manager.
AllowWideContent indicates whether the content should be displayed in a way that’s amenable to wider viewports.
licensePolicyPage is used to generate the static license policy page.
newBasePage returns a base page for the given request and title.
errorPage contains fields for rendering a HTTP error page.
PanicHandler returns an http.HandlerFunc that can be used in HTTP middleware. It returns an error if something goes wrong pre-rendering the error template.
func ( *Server) () ( http.HandlerFunc,  error) {
	defer derrors.Wrap(&, "PanicHandler")
	 := http.StatusInternalServerError
	,  := .renderErrorPage(context.Background(), , "error.tmpl", nil)
	if  != nil {
		return nil, 
	}
	return func( http.ResponseWriter,  *http.Request) {
		.WriteHeader()
		if ,  := io.Copy(, bytes.NewReader());  != nil {
			log.Errorf(.Context(), "Error copying panic template to ResponseWriter: %v", )
		}
	}, nil
}

type serverError struct {
	status       int    // HTTP status code
	responseText string // Response text to the user
	epage        *errorPage
	err          error // wrapped error
}

func ( *serverError) () string {
	return fmt.Sprintf("%d (%s): %v (epage=%v)", .status, http.StatusText(.status), .err, .epage)
}

func ( *serverError) () error {
	return .err
}

func ( *Server) ( func( http.ResponseWriter,  *http.Request,  internal.DataSource) error) http.HandlerFunc {
Obtain a DataSource to use for this request.
		 := .getDataSource(.Context())
		if  := (, , );  != nil {
			.serveError(, , )
		}
	}
}

func ( *Server) ( http.ResponseWriter,  *http.Request,  error) {
	 := .Context()
	var  *serverError
	if !errors.As(, &) {
		 = &serverError{status: http.StatusInternalServerError, err: }
	}
	if .status == http.StatusInternalServerError {
		log.Error(, )
		.reportError(, , , )
	} else {
		log.Infof(, "returning %d (%s) for error %v", .status, http.StatusText(.status), )
	}
	if .responseText == "" {
		.responseText = http.StatusText(.status)
	}
	if .Method == http.MethodPost {
		http.Error(, .responseText, .status)
		return
	}
	.serveErrorPage(, , .status, .epage)
}
reportError sends the error to the GCP Error Reporting service.
func ( *Server) ( context.Context,  error,  http.ResponseWriter,  *http.Request) {
	if .reportingClient == nil {
		return
Extract the stack trace from the error if there is one.
	var  []byte
	if  := (*derrors.StackError)(nil); errors.As(, &) {
		 = .Stack
	}
	.reportingClient.Report(errorreporting.Entry{
		Error: ,
		Req:   ,
		Stack: ,
	})
Bypass the error-reporting middleware.
	.Header().Set(config.BypassErrorReportingHeader, "true")
}

func ( *Server) ( http.ResponseWriter,  *http.Request,  int,  *errorPage) {
	 := "error.tmpl"
	if  != nil {
If the basePage was properly created using newBasePage, both AppVersionLabel and GoogleTagManagerID should always be set.
			.basePage = .newBasePage(, "")
		}
		if .templateName != "" {
			 = .templateName
		}
	} else {
		 = &errorPage{
			basePage: .newBasePage(, ""),
		}
	}
	,  := .renderErrorPage(.Context(), , , )
	if  != nil {
		log.Errorf(.Context(), "s.renderErrorPage(w, %d, %v): %v", , , )
		 = .errorPage
		 = http.StatusInternalServerError
	}

	.WriteHeader()
	if ,  := io.Copy(, bytes.NewReader());  != nil {
		log.Errorf(.Context(), "Error copying template %q buffer to ResponseWriter: %v", , )
	}
}
renderErrorPage executes error.tmpl with the given errorPage
func ( *Server) ( context.Context,  int,  string,  *errorPage) ([]byte, error) {
	 := fmt.Sprintf("%d %s", , http.StatusText())
	if  == nil {
		 = &errorPage{}
	}
	if .messageTemplate.String() == "" {
		.messageTemplate = template.MakeTrustedTemplate(`<h3 class="Error-message">{{.}}</h3>`)
	}
	if .MessageData == nil {
		.MessageData = 
	}
	if .HTMLTitle == "" {
		.HTMLTitle = 
	}
	if  == "" {
		 = "error.tmpl"
	}

	,  := .findTemplate()
	if  != nil {
		return nil, 
	}
	,  := .Clone()
	if  != nil {
		return nil, 
	}
	_,  = .New("message").ParseFromTrustedTemplate(.messageTemplate)
	if  != nil {
		return nil, 
	}

	return executeTemplate(, , , )
}
servePage is used to execute all templates for a *Server.
func ( *Server) ( context.Context,  http.ResponseWriter,  string,  interface{}) {
	defer middleware.ElapsedStat(, "servePage")()

	,  := .renderPage(, , )
	if  != nil {
		log.Errorf(, "s.renderPage(%q, %+v): %v", , , )
		.WriteHeader(http.StatusInternalServerError)
		 = .errorPage
	}
	if ,  := io.Copy(, bytes.NewReader());  != nil {
		log.Errorf(, "Error copying template %q buffer to ResponseWriter: %v", , )
		.WriteHeader(http.StatusInternalServerError)
	}
}
renderPage executes the given templateName with page.
func ( *Server) ( context.Context,  string,  interface{}) ([]byte, error) {
	defer middleware.ElapsedStat(, "renderPage")()

	,  := .findTemplate()
	if  != nil {
		return nil, 
	}
	return executeTemplate(, , , )
}

func ( *Server) ( string) (*template.Template, error) {
	if .devMode {
		.mu.Lock()
		defer .mu.Unlock()
		var  error
		.templates,  = parsePageTemplates(.templateDir)
		if  != nil {
			return nil, fmt.Errorf("error parsing templates: %v", )
		}
	}
	 := .templates[]
	if  == nil {
		return nil, fmt.Errorf("BUG: s.templates[%q] not found", )
	}
	return , nil
}

func ( context.Context,  string,  *template.Template,  interface{}) ([]byte, error) {
	var  bytes.Buffer
	if  := .Execute(&, );  != nil {
		log.Errorf(, "Error executing page template %q: %v", , )
		return nil, 
	}
	return .Bytes(), nil
}

var templateFuncs = template.FuncMap{
	"add": func(,  int) int { return  +  },
	"pluralize": func( int,  string) string {
		if  == 1 {
			return 
		}
		return  + "s"
	},
	"commaseparate": func( []string) string {
		return strings.Join(, ", ")
	},
	"stripscheme": stripScheme,
}

func ( string) string {
	if  := strings.Index(, "://");  > 0 {
		return [+len("://"):]
	}
	return 
}
parsePageTemplates parses html templates contained in the given base directory in order to generate a map of Name->*template.Template. Separate templates are used so that certain contextual functions (e.g. templateName) can be bound independently for each page.
func ( template.TrustedSource) (map[string]*template.Template, error) {
	 := template.TrustedSourceFromConstant
	 := template.TrustedSourceJoin

	 := [][]template.TrustedSource{
		{("badge.tmpl")},
		{("error.tmpl")},
		{("fetch.tmpl")},
		{("index.tmpl")},
		{("license_policy.tmpl")},
		{("search.tmpl")},
		{("search_help.tmpl")},
		{("unit_details.tmpl"), ("unit.tmpl")},
		{("unit_importedby.tmpl"), ("unit.tmpl")},
		{("unit_imports.tmpl"), ("unit.tmpl")},
		{("unit_licenses.tmpl"), ("unit.tmpl")},
		{("unit_versions.tmpl"), ("unit.tmpl")},
	}

	 := make(map[string]*template.Template)
	for ,  := range  {
		,  := template.New("base.tmpl").Funcs(templateFuncs).ParseFilesFromTrustedSources((, ("html"), ("base.tmpl")))
		if  != nil {
			return nil, fmt.Errorf("ParseFiles: %v", )
		}
		 := (, ("html"), ("helpers"), ("*.tmpl"))
		if ,  := .ParseGlobFromTrustedSource();  != nil {
			return nil, fmt.Errorf("ParseGlob(%q): %v", , )
		}

		var  []template.TrustedSource
		for ,  := range  {
			 = append(, (, ("html"), ("pages"), ))
		}
		if ,  := .ParseFilesFromTrustedSources(...);  != nil {
			return nil, fmt.Errorf("ParseFilesFromTrustedSources(%v): %v", , )
		}
		[[0].String()] = 
	}

	 := [][]template.TrustedSource{
		{("styleguide"), ("main-layout")},
	}
	for ,  := range  {
		,  := template.New("base.tmpl").Funcs(templateFuncs).ParseFilesFromTrustedSources((, ("base/base.tmpl")))
		if  != nil {
			return nil, fmt.Errorf("ParseFilesFromTrustedSources: %v", )
		}
		 := (, ("**/*.partial.tmpl"))
		if ,  := .ParseGlobFromTrustedSource();  != nil {
			return nil, fmt.Errorf("ParseGlobFromTrustedSource(%q): %v", , )
		}
		var  []template.TrustedSource
		for ,  := range  {
			if ,  := .ParseGlobFromTrustedSource((, , ("*.tmpl")));  != nil {
				return nil, fmt.Errorf("ParseGlobFromTrustedSource(%v): %v", , )
			}
		}
		[[0].String()] = 
	}

	return , nil
}

func ( *Server) () http.Handler {
	 := .staticPath.String()
In dev mode compile TypeScript files into minified JavaScript files and rebuild them on file changes.
	if .devMode {
		 := context.Background()
		,  := static.Build(static.Config{StaticPath:  + "/js", Watch: true, Write: true})
		if  != nil {
			log.Error(, )
		}
	}
	return http.StripPrefix("/static/", http.FileServer(http.Dir()))