in internal/frontend/404.go [46:164]
func (s *Server) servePathNotFoundPage(w http.ResponseWriter, r *http.Request,
ds internal.DataSource, fullPath, modulePath, requestedVersion string) (err error) {
defer derrors.Wrap(&err, "servePathNotFoundPage(w, r, %q, %q)", fullPath, requestedVersion)
db, ok := ds.(*postgres.DB)
if !ok {
return datasourceNotSupportedErr()
}
ctx := r.Context()
if stdlib.Contains(fullPath) {
var path string
path, err = stdlibPathForShortcut(ctx, db, fullPath)
if err != nil {
// Log the error, but prefer a "path not found" error for a
// better user experience.
log.Error(ctx, err)
}
if path != "" {
http.Redirect(w, r, fmt.Sprintf("/%s", path), http.StatusFound)
return
}
if experiment.IsActive(ctx, internal.ExperimentEnableStdFrontendFetch) {
return &serverError{
status: http.StatusNotFound,
epage: &errorPage{
templateName: "fetch",
MessageData: stdlib.ModulePath,
},
}
}
return &serverError{status: http.StatusNotFound}
}
fr, err := previousFetchStatusAndResponse(ctx, db, fullPath, modulePath, requestedVersion)
if err != nil {
// If an error occurred, it means that we have never tried to fetch
// this path before or an error occurred when we tried to
// gather data about this 404.
//
// If the latter, log the error.
// In either case, give the user the option to fetch that path.
if !errors.Is(err, derrors.NotFound) && !errors.Is(err, derrors.InvalidArgument) {
log.Error(ctx, err)
}
return pathNotFoundError(ctx, fullPath, requestedVersion)
}
// If we've reached this point, we know that we've seen this path before.
// Show a relevant page or redirect the use based on the previous fetch
// response.
switch fr.status {
case http.StatusOK, derrors.ToStatus(derrors.HasIncompletePackages):
// We will only reach a 2xx status if we found a row in version_map
// matching exactly the requested path.
if fr.resolvedVersion != requestedVersion {
u := constructUnitURL(fullPath, fr.goModPath, fr.resolvedVersion)
http.Redirect(w, r, u, http.StatusFound)
return
}
// For some reason version_map is telling us that the path@version
// exists, but earlier in this flow we didn't find it in the units
// table.
//
// Return the fetch page so the user can try requesting again, and log
// an error.
log.Errorf(ctx, "version_map reports that %s@%s has status=%d, but this was not found before reaching servePathNotFoundPage",
fullPath, requestedVersion, fr.status)
return pathNotFoundError(ctx, fullPath, requestedVersion)
case http.StatusFound, derrors.ToStatus(derrors.AlternativeModule):
if fr.goModPath == fullPath {
// The redirectPath and the fullpath are the same. Do not redirect
// to avoid ending up in a loop.
return errUnitNotFoundWithoutFetch
}
vm, err := db.GetVersionMap(ctx, fr.goModPath, version.Latest)
if (err != nil && !errors.Is(err, derrors.NotFound)) ||
(vm != nil && vm.Status != http.StatusOK) {
// We attempted to fetch the canonical module path before and were
// not successful. Do not redirect this request.
return errUnitNotFoundWithoutFetch
}
u := constructUnitURL(fr.goModPath, fr.goModPath, version.Latest)
cookie.Set(w, cookie.AlternativeModuleFlash, fullPath, u)
http.Redirect(w, r, u, http.StatusFound)
return nil
case http.StatusInternalServerError:
return pathNotFoundError(ctx, fullPath, requestedVersion)
default:
if u := githubPathRedirect(fullPath); u != "" {
http.Redirect(w, r, u, http.StatusFound)
return
}
// If a module has a status of 404, but s.taskIDChangeInterval has
// passed, allow the module to be refetched.
if fr.status == http.StatusNotFound && time.Since(fr.updatedAt) > s.taskIDChangeInterval {
return pathNotFoundError(ctx, fullPath, requestedVersion)
}
// Redirect to the search result page for an empty directory that is above nested modules.
// See https://golang.org/issue/43725 for context.
nm, err := ds.GetNestedModules(ctx, fullPath)
if err == nil && len(nm) > 0 {
http.Redirect(w, r, "/search?q="+url.QueryEscape(fullPath), http.StatusFound)
return nil
}
return &serverError{
status: fr.status,
epage: &errorPage{
messageTemplate: uncheckedconversions.TrustedTemplateFromStringKnownToSatisfyTypeContract(`
<h3 class="Error-message">{{.StatusText}}</h3>
<p class="Error-message">` + html.UnescapeString(fr.responseText) + `</p>`),
MessageData: struct{ StatusText string }{http.StatusText(fr.status)},
},
}
}
}