internal/frontend: redirect alternative module paths

When a fetch request reports that a path is an alternative module path,
redirect the user to the canonical path.

For golang/go#43518

Change-Id: I0835a46e8f104ed6c404fa8184578487af55cc54
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/281872
Trust: Julie Qiu <julie@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/frontend/404.go b/internal/frontend/404.go
index 6b98ca2..ed8ab80 100644
--- a/internal/frontend/404.go
+++ b/internal/frontend/404.go
@@ -63,6 +63,10 @@
 		}
 		return pathNotFoundError(fullPath, requestedVersion)
 	}
+	if fr.goModPath != fr.modulePath && fr.status == derrors.ToStatus(derrors.AlternativeModule) {
+		http.Redirect(w, r, constructUnitURL(fr.goModPath, fr.goModPath, internal.LatestVersion), http.StatusFound)
+		return
+	}
 	return &serverError{
 		status: fr.status,
 		epage: &errorPage{
diff --git a/internal/frontend/server_test.go b/internal/frontend/server_test.go
index 005c5ff..30c3b11 100644
--- a/internal/frontend/server_test.go
+++ b/internal/frontend/server_test.go
@@ -1152,7 +1152,7 @@
 	alternativeModule := &internal.VersionMap{
 		ModulePath:       "module.path/alternative",
 		GoModPath:        sample.ModulePath,
-		RequestedVersion: sample.VersionString,
+		RequestedVersion: internal.LatestVersion,
 		ResolvedVersion:  sample.VersionString,
 		Status:           derrors.ToStatus(derrors.AlternativeModule),
 	}
@@ -1167,7 +1167,7 @@
 	}{
 		{"not found", "/invalid-page", http.StatusNotFound},
 		{"bad request", "/gocloud.dev/@latest/blob", http.StatusBadRequest},
-		{"alternative module", "/" + alternativeModule.ModulePath, http.StatusNotFound},
+		{"alternative module", "/" + alternativeModule.ModulePath, http.StatusFound},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			w := httptest.NewRecorder()