gddo-server: add initial redirect logic

Logic is added to redirect specific paths to pkg.go.dev.

Change-Id: I49b3d446e99c5684dd9121322b822f84b1cda1be
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/281732
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 d0f12a5..57eac28 100644
--- a/gddo-server/main.go
+++ b/gddo-server/main.go
@@ -961,9 +961,9 @@
 	}
 
 	mux.Handle("/-/about", handler(pkgGoDevRedirectHandler(s.serveAbout)))
-	mux.Handle("/-/bot", handler(s.serveBot))
+	mux.Handle("/-/bot", handler(pkgGoDevRedirectHandler(s.serveBot)))
 	mux.Handle("/-/go", handler(pkgGoDevRedirectHandler(s.serveGoIndex)))
-	mux.Handle("/-/subrepo", handler(s.serveGoSubrepoIndex))
+	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"))
@@ -1075,6 +1075,10 @@
 }
 
 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
diff --git a/gddo-server/pkgsite.go b/gddo-server/pkgsite.go
index 3e185c9..5c883f8 100644
--- a/gddo-server/pkgsite.go
+++ b/gddo-server/pkgsite.go
@@ -187,12 +187,15 @@
 // can be turned on/off using a query param.
 func pkgGoDevRedirectHandler(f func(http.ResponseWriter, *http.Request) error) func(http.ResponseWriter, *http.Request) error {
 	return func(w http.ResponseWriter, r *http.Request) error {
+		if shouldRedirectRequest(r) {
+			http.Redirect(w, r, pkgGoDevURL(r.URL).String(), http.StatusFound)
+			return nil
+		}
+
 		if userReturningFromPkgGoDev(r) {
 			return f(w, r)
 		}
-
 		redirectParam := r.FormValue(pkgGoDevRedirectParam)
-
 		if redirectParam == pkgGoDevRedirectOn {
 			cookie := &http.Cookie{Name: pkgGoDevRedirectCookie, Value: redirectParam, Path: "/"}
 			http.SetCookie(w, cookie)
@@ -201,16 +204,39 @@
 			cookie := &http.Cookie{Name: pkgGoDevRedirectCookie, Value: "", MaxAge: -1, Path: "/"}
 			http.SetCookie(w, cookie)
 		}
-
 		if !shouldRedirectToPkgGoDev(r) {
 			return f(w, r)
 		}
-
 		http.Redirect(w, r, pkgGoDevURL(r.URL).String(), http.StatusFound)
 		return nil
 	}
 }
 
+// alwaysRedirectPaths is a set of paths that are always redirected to
+// pkg.go.dev.
+var alwaysRedirectPaths = map[string]bool{
+	"/-/about": true,
+}
+
+func shouldRedirectRequest(r *http.Request) bool {
+	// Requests to api.godoc.org and talks.godoc.org are not redirected.
+	if strings.HasPrefix(r.URL.Host, "api") || strings.HasPrefix(r.URL.Host, "talks") {
+		return false
+	}
+	// Badge SVGs will be redirected last.
+	_, isSVG := r.URL.Query()["status.svg"]
+	_, isPNG := r.URL.Query()["status.png"]
+	if isSVG || isPNG {
+		return false
+	}
+
+	if alwaysRedirectPaths[r.URL.Path] {
+		return true
+	}
+	// TODO: redirect based on rollout percentage.
+	return false
+}
+
 const goGithubRepoURLPath = "/github.com/golang/go"
 
 func pkgGoDevURL(godocURL *url.URL) *url.URL {
diff --git a/gddo-server/pkgsite_test.go b/gddo-server/pkgsite_test.go
index ac2808a..18ffe08 100644
--- a/gddo-server/pkgsite_test.go
+++ b/gddo-server/pkgsite_test.go
@@ -18,10 +18,6 @@
 )
 
 func TestHandlePkgGoDevRedirect(t *testing.T) {
-	handler := pkgGoDevRedirectHandler(func(w http.ResponseWriter, r *http.Request) error {
-		return nil
-	})
-
 	for _, test := range []struct {
 		name, url, wantLocationHeader, wantSetCookieHeader string
 		wantStatusCode                                     int
@@ -70,12 +66,21 @@
 			cookie:         &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
 			wantStatusCode: http.StatusOK,
 		},
+		{
+			name:               "always redirect /-/about",
+			url:                "http://godoc.org/-/about",
+			wantLocationHeader: "https://pkg.go.dev/about?utm_source=godoc",
+			wantStatusCode:     http.StatusFound,
+		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			req := httptest.NewRequest("GET", test.url, nil)
 			if test.cookie != nil {
 				req.AddCookie(test.cookie)
 			}
+			handler := pkgGoDevRedirectHandler(func(w http.ResponseWriter, r *http.Request) error {
+				return nil
+			})
 
 			w := httptest.NewRecorder()
 			err := handler(w, req)
@@ -87,11 +92,9 @@
 			if got, want := resp.Header.Get("Location"), test.wantLocationHeader; got != want {
 				t.Errorf("Location header mismatch: got %q; want %q", got, want)
 			}
-
 			if got, want := resp.Header.Get("Set-Cookie"), test.wantSetCookieHeader; got != want {
 				t.Errorf("Set-Cookie header mismatch: got %q; want %q", got, want)
 			}
-
 			if got, want := resp.StatusCode, test.wantStatusCode; got != want {
 				t.Errorf("Status code mismatch: got %d; want %d", got, want)
 			}