gddo-server: move code related to pkgsite to pkgsite.go
Pure code in motion.
Change-Id: If883c14b0d7ca1ced8298980111e11cea9614510
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/274696
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 f8f7b4b..47ca9e0 100644
--- a/gddo-server/main.go
+++ b/gddo-server/main.go
@@ -19,7 +19,6 @@
"io"
"log"
"net/http"
- "net/url"
"os"
"path"
"regexp"
@@ -1020,15 +1019,6 @@
return s, nil
}
-const (
- pkgGoDevRedirectCookie = "pkggodev-redirect"
- pkgGoDevRedirectParam = "redirect"
- pkgGoDevRedirectOn = "on"
- pkgGoDevRedirectOff = "off"
- pkgGoDevHost = "pkg.go.dev"
- teeproxyHost = "teeproxy-dot-go-discovery.appspot.com"
-)
-
type responseWriter struct {
http.ResponseWriter
status int
@@ -1065,54 +1055,6 @@
}
}
-func makePkgGoDevRequest(r *http.Request, latency time.Duration, isRobot bool, status int) error {
- event := newGDDOEvent(r, latency, isRobot, status)
- b, err := json.Marshal(event)
- if err != nil {
- return fmt.Errorf("json.Marshal(%v): %v", event, err)
- }
-
- teeproxyURL := url.URL{Scheme: "https", Host: teeproxyHost}
- if _, err := http.Post(teeproxyURL.String(), jsonMIMEType, bytes.NewReader(b)); err != nil {
- return fmt.Errorf("http.Post(%q, %q, %v): %v", teeproxyURL.String(), jsonMIMEType, event, err)
- }
- log.Printf("makePkgGoDevRequest: request made to %q for %+v", teeproxyURL.String(), event)
- return nil
-}
-
-type gddoEvent struct {
- Host string
- Path string
- Status int
- URL string
- Header http.Header
- Latency time.Duration
- IsRobot bool
- UsePkgGoDev bool
-}
-
-func newGDDOEvent(r *http.Request, latency time.Duration, isRobot bool, status int) *gddoEvent {
- targetURL := url.URL{
- Scheme: "https",
- Host: r.URL.Host,
- Path: r.URL.Path,
- RawQuery: r.URL.RawQuery,
- }
- if targetURL.Host == "" && r.Host != "" {
- targetURL.Host = r.Host
- }
- return &gddoEvent{
- Host: targetURL.Host,
- Path: r.URL.Path,
- Status: status,
- URL: targetURL.String(),
- Header: r.Header,
- Latency: latency,
- IsRobot: isRobot,
- UsePkgGoDev: shouldRedirectToPkgGoDev(r),
- }
-}
-
func (s *server) logRequestStart(req *http.Request) {
if s.gceLogger == nil {
return
@@ -1138,92 +1080,6 @@
})
}
-func userReturningFromPkgGoDev(req *http.Request) bool {
- return req.FormValue("utm_source") == "backtogodoc"
-}
-
-func shouldRedirectToPkgGoDev(req *http.Request) bool {
- // API requests are not redirected.
- if strings.HasPrefix(req.URL.Host, "api") {
- return false
- }
- redirectParam := req.FormValue(pkgGoDevRedirectParam)
- if redirectParam == pkgGoDevRedirectOn || redirectParam == pkgGoDevRedirectOff {
- return redirectParam == pkgGoDevRedirectOn
- }
- cookie, err := req.Cookie(pkgGoDevRedirectCookie)
- return (err == nil && cookie.Value == pkgGoDevRedirectOn)
-}
-
-// pkgGoDevRedirectHandler redirects requests from godoc.org to pkg.go.dev,
-// based on whether a cookie is set for pkggodev-redirect. The cookie
-// 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 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)
- }
- if redirectParam == pkgGoDevRedirectOff {
- 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
- }
-}
-
-func pkgGoDevURL(godocURL *url.URL) *url.URL {
- u := &url.URL{Scheme: "https", Host: pkgGoDevHost}
- q := url.Values{"utm_source": []string{"godoc"}}
-
- if strings.Contains(godocURL.Path, "/vendor/") || strings.HasSuffix(godocURL.Path, "/vendor") {
- u.Path = "/"
- u.RawQuery = q.Encode()
- return u
- }
-
- switch godocURL.Path {
- case "/-/go":
- u.Path = "/std"
- q.Add("tab", "packages")
- case "/-/about":
- u.Path = "/about"
- case "/":
- if qparam := godocURL.Query().Get("q"); qparam != "" {
- u.Path = "/search"
- q.Set("q", qparam)
- } else {
- u.Path = "/"
- }
- default:
- {
- u.Path = godocURL.Path
- if _, ok := godocURL.Query()["imports"]; ok {
- q.Set("tab", "imports")
- } else if _, ok := godocURL.Query()["importers"]; ok {
- q.Set("tab", "importedby")
- } else {
- q.Set("tab", "doc")
- }
- }
- }
-
- u.RawQuery = q.Encode()
- return u
-}
-
func main() {
ctx := context.Background()
v, err := loadConfig(ctx, os.Args)
diff --git a/gddo-server/main_test.go b/gddo-server/main_test.go
index 9267349..7c4bc78 100644
--- a/gddo-server/main_test.go
+++ b/gddo-server/main_test.go
@@ -7,14 +7,7 @@
package main
import (
- "bufio"
- "net/http"
- "net/http/httptest"
- "net/url"
- "strings"
"testing"
-
- "github.com/google/go-cmp/cmp"
)
var robotTests = []string{
@@ -40,416 +33,3 @@
}
}
}
-
-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
- cookie *http.Cookie
- }{
- {
- name: "test pkggodev-redirect param is on",
- url: "http://godoc.org/net/http?redirect=on",
- wantLocationHeader: "https://pkg.go.dev/net/http?tab=doc&utm_source=godoc",
- wantSetCookieHeader: "pkggodev-redirect=on; Path=/",
- wantStatusCode: http.StatusFound,
- },
- {
- name: "test pkggodev-redirect param is off",
- url: "http://godoc.org/net/http?redirect=off",
- wantLocationHeader: "",
- wantSetCookieHeader: "pkggodev-redirect=; Path=/; Max-Age=0",
- wantStatusCode: http.StatusOK,
- },
- {
- name: "test pkggodev-redirect param is unset",
- url: "http://godoc.org/net/http",
- wantLocationHeader: "",
- wantSetCookieHeader: "",
- wantStatusCode: http.StatusOK,
- },
- {
- name: "toggle enabled pkggodev-redirect cookie",
- url: "http://godoc.org/net/http?redirect=off",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "true"},
- wantLocationHeader: "",
- wantSetCookieHeader: "pkggodev-redirect=; Path=/; Max-Age=0",
- wantStatusCode: http.StatusOK,
- },
- {
- name: "pkggodev-redirect enabled cookie should redirect",
- url: "http://godoc.org/net/http",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
- wantLocationHeader: "https://pkg.go.dev/net/http?tab=doc&utm_source=godoc",
- wantSetCookieHeader: "",
- wantStatusCode: http.StatusFound,
- },
- {
- name: "do not redirect if user is returning from pkg.go.dev",
- url: "http://godoc.org/net/http?utm_source=backtogodoc",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
- wantStatusCode: http.StatusOK,
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- req := httptest.NewRequest("GET", test.url, nil)
- if test.cookie != nil {
- req.AddCookie(test.cookie)
- }
-
- w := httptest.NewRecorder()
- err := handler(w, req)
- if err != nil {
- t.Fatal(err)
- }
- resp := w.Result()
-
- 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)
- }
- })
- }
-}
-
-func TestGodoc(t *testing.T) {
- testCases := []struct {
- from, to string
- }{
- {
- from: "https://godoc.org/-/about",
- to: "https://pkg.go.dev/about?utm_source=godoc",
- },
- {
- from: "https://godoc.org/-/go",
- to: "https://pkg.go.dev/std?tab=packages&utm_source=godoc",
- },
- {
- from: "https://godoc.org/?q=foo",
- to: "https://pkg.go.dev/search?q=foo&utm_source=godoc",
- },
- {
- from: "https://godoc.org/cloud.google.com/go/storage",
- to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=doc&utm_source=godoc",
- },
- {
- from: "https://godoc.org/cloud.google.com/go/storage?imports",
- to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=imports&utm_source=godoc",
- },
- {
- from: "https://godoc.org/cloud.google.com/go/storage?importers",
- to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=importedby&utm_source=godoc",
- },
- {
- from: "https://godoc.org/golang.org/x/vgo/vendor/cmd/go/internal/modfile",
- to: "https://pkg.go.dev/?utm_source=godoc",
- },
- {
- from: "https://godoc.org/golang.org/x/vgo/vendor",
- to: "https://pkg.go.dev/?utm_source=godoc",
- },
- }
-
- for _, tc := range testCases {
- u, err := url.Parse(tc.from)
- if err != nil {
- t.Errorf("url.Parse(%q): %v", tc.from, err)
- continue
- }
- to := pkgGoDevURL(u)
- if got, want := to.String(), tc.to; got != want {
- t.Errorf("pkgGoDevURL(%q) = %q; want %q", u, got, want)
- }
- }
-}
-
-func TestNewGDDOEvent(t *testing.T) {
- for _, test := range []struct {
- name string
- url string
- cookie *http.Cookie
- want *gddoEvent
- }{
- {
- name: "home page request",
- url: "https://godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "",
- UsePkgGoDev: false,
- },
- },
- {
- name: "home page request with cookie on should redirect",
- url: "https://godoc.org",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "",
- UsePkgGoDev: true,
- },
- },
- {
- name: "about page request",
- url: "https://godoc.org/-/about",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/-/about",
- UsePkgGoDev: false,
- },
- },
- {
- name: "request with search query parameter",
- url: "https://godoc.org/?q=test",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/",
- UsePkgGoDev: false,
- },
- },
- {
- name: "package page request",
- url: "https://godoc.org/net/http",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- UsePkgGoDev: false,
- },
- },
- {
- name: "package page request with wrong cookie on should not redirect",
- url: "https://godoc.org/net/http",
- cookie: &http.Cookie{Name: "bogus-cookie", Value: "on"},
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- UsePkgGoDev: false,
- },
- },
- {
- name: "package page request with query parameter off should not redirect",
- url: "https://godoc.org/net/http?redirect=off",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- UsePkgGoDev: false,
- },
- },
- {
- name: "package page request with cookie on should redirect",
- url: "https://godoc.org/net/http",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- UsePkgGoDev: true,
- },
- },
- {
- name: "package page request with query parameter on should redirect",
- url: "https://godoc.org/net/http?redirect=on",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: ""},
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- UsePkgGoDev: true,
- },
- },
- {
- name: "api request",
- url: "https://api.godoc.org/imports/net/http",
- want: &gddoEvent{
- Host: "api.godoc.org",
- Path: "/imports/net/http",
- UsePkgGoDev: false,
- },
- },
- {
- name: "api requests should never redirect, even with cookie on",
- url: "https://api.godoc.org/imports/net/http",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
- want: &gddoEvent{
- Host: "api.godoc.org",
- Path: "/imports/net/http",
- UsePkgGoDev: false,
- },
- },
- {
- name: "api requests should never redirect, even with query parameter on",
- url: "https://api.godoc.org/imports/net/http?redirect=on",
- cookie: &http.Cookie{Name: "pkggodev-redirect", Value: ""},
- want: &gddoEvent{
- Host: "api.godoc.org",
- Path: "/imports/net/http",
- UsePkgGoDev: false,
- },
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- want := test.want
- want.Latency = 100
- want.URL = test.url
- want.Header = http.Header{}
- want.IsRobot = true
- r := httptest.NewRequest("GET", test.url, nil)
- if test.cookie != nil {
- r.AddCookie(test.cookie)
- want.Header.Add("Cookie", test.cookie.String())
- }
- got := newGDDOEvent(r, want.Latency, want.IsRobot, http.StatusOK)
- want.Status = http.StatusOK
- if diff := cmp.Diff(want, got); diff != "" {
- t.Fatalf("mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
-
-func TestNewGDDOEventFromRequests(t *testing.T) {
- for _, test := range []struct {
- name string
- requestURI string
- host string
- want *gddoEvent
- }{
- {
- name: "absolute index path",
- requestURI: "https://godoc.org",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "",
- URL: "https://godoc.org",
- },
- },
- {
- name: "absolute index path with trailing slash",
- requestURI: "https://godoc.org/",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/",
- URL: "https://godoc.org/",
- },
- },
- {
- name: "relative index path",
- requestURI: "/",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/",
- URL: "https://godoc.org/",
- },
- },
- {
- name: "absolute about path",
- requestURI: "https://godoc.org/-/about",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/-/about",
- URL: "https://godoc.org/-/about",
- },
- },
- {
- name: "relative about path",
- requestURI: "/-/about",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/-/about",
- URL: "https://godoc.org/-/about",
- },
- },
- {
- name: "absolute package path",
- requestURI: "https://godoc.org/net/http",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- URL: "https://godoc.org/net/http",
- },
- },
- {
- name: "relative package path",
- requestURI: "/net/http",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- URL: "https://godoc.org/net/http",
- },
- },
- {
- name: "absolute path with query parameters",
- requestURI: "https://godoc.org/net/http?q=test",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- URL: "https://godoc.org/net/http?q=test",
- },
- },
- {
- name: "relative path with query parameters",
- requestURI: "/net/http?q=test",
- host: "godoc.org",
- want: &gddoEvent{
- Host: "godoc.org",
- Path: "/net/http",
- URL: "https://godoc.org/net/http?q=test",
- },
- },
- {
- name: "absolute api path",
- requestURI: "https://api.godoc.org/imports/net/http",
- host: "api.godoc.org",
- want: &gddoEvent{
- Host: "api.godoc.org",
- Path: "/imports/net/http",
- URL: "https://api.godoc.org/imports/net/http",
- },
- },
- {
- name: "relative api path",
- requestURI: "/imports/net/http",
- host: "api.godoc.org",
- want: &gddoEvent{
- Host: "api.godoc.org",
- Path: "/imports/net/http",
- URL: "https://api.godoc.org/imports/net/http",
- },
- },
- } {
- t.Run(test.name, func(t *testing.T) {
- want := test.want
- want.Latency = 100
- want.Header = http.Header{}
- want.IsRobot = false
- requestLine := "GET " + test.requestURI + " HTTP/1.1\r\nHost: " + test.host + "\r\n\r\n"
- req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(requestLine)))
- if err != nil {
- t.Fatal("invalid NewRequest arguments; " + err.Error())
- }
- got := newGDDOEvent(req, want.Latency, want.IsRobot, http.StatusOK)
- want.Status = http.StatusOK
- if diff := cmp.Diff(want, got); diff != "" {
- t.Fatalf("mismatch (-want +got):\n%s", diff)
- }
- })
- }
-}
diff --git a/gddo-server/pkgsite.go b/gddo-server/pkgsite.go
new file mode 100644
index 0000000..d5ead96
--- /dev/null
+++ b/gddo-server/pkgsite.go
@@ -0,0 +1,161 @@
+// Copyright 2020 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.
+
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+func makePkgGoDevRequest(r *http.Request, latency time.Duration, isRobot bool, status int) error {
+ event := newGDDOEvent(r, latency, isRobot, status)
+ b, err := json.Marshal(event)
+ if err != nil {
+ return fmt.Errorf("json.Marshal(%v): %v", event, err)
+ }
+
+ teeproxyURL := url.URL{Scheme: "https", Host: teeproxyHost}
+ if _, err := http.Post(teeproxyURL.String(), jsonMIMEType, bytes.NewReader(b)); err != nil {
+ return fmt.Errorf("http.Post(%q, %q, %v): %v", teeproxyURL.String(), jsonMIMEType, event, err)
+ }
+ log.Printf("makePkgGoDevRequest: request made to %q for %+v", teeproxyURL.String(), event)
+ return nil
+}
+
+type gddoEvent struct {
+ Host string
+ Path string
+ Status int
+ URL string
+ Header http.Header
+ Latency time.Duration
+ IsRobot bool
+ UsePkgGoDev bool
+}
+
+func newGDDOEvent(r *http.Request, latency time.Duration, isRobot bool, status int) *gddoEvent {
+ targetURL := url.URL{
+ Scheme: "https",
+ Host: r.URL.Host,
+ Path: r.URL.Path,
+ RawQuery: r.URL.RawQuery,
+ }
+ if targetURL.Host == "" && r.Host != "" {
+ targetURL.Host = r.Host
+ }
+ return &gddoEvent{
+ Host: targetURL.Host,
+ Path: r.URL.Path,
+ Status: status,
+ URL: targetURL.String(),
+ Header: r.Header,
+ Latency: latency,
+ IsRobot: isRobot,
+ UsePkgGoDev: shouldRedirectToPkgGoDev(r),
+ }
+}
+
+func userReturningFromPkgGoDev(req *http.Request) bool {
+ return req.FormValue("utm_source") == "backtogodoc"
+}
+
+const (
+ pkgGoDevRedirectCookie = "pkggodev-redirect"
+ pkgGoDevRedirectParam = "redirect"
+ pkgGoDevRedirectOn = "on"
+ pkgGoDevRedirectOff = "off"
+ pkgGoDevHost = "pkg.go.dev"
+ teeproxyHost = "teeproxy-dot-go-discovery.appspot.com"
+)
+
+func shouldRedirectToPkgGoDev(req *http.Request) bool {
+ // API requests are not redirected.
+ if strings.HasPrefix(req.URL.Host, "api") {
+ return false
+ }
+ redirectParam := req.FormValue(pkgGoDevRedirectParam)
+ if redirectParam == pkgGoDevRedirectOn || redirectParam == pkgGoDevRedirectOff {
+ return redirectParam == pkgGoDevRedirectOn
+ }
+ cookie, err := req.Cookie(pkgGoDevRedirectCookie)
+ return (err == nil && cookie.Value == pkgGoDevRedirectOn)
+}
+
+// pkgGoDevRedirectHandler redirects requests from godoc.org to pkg.go.dev,
+// based on whether a cookie is set for pkggodev-redirect. The cookie
+// 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 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)
+ }
+ if redirectParam == pkgGoDevRedirectOff {
+ 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
+ }
+}
+
+func pkgGoDevURL(godocURL *url.URL) *url.URL {
+ u := &url.URL{Scheme: "https", Host: pkgGoDevHost}
+ q := url.Values{"utm_source": []string{"godoc"}}
+
+ if strings.Contains(godocURL.Path, "/vendor/") || strings.HasSuffix(godocURL.Path, "/vendor") {
+ u.Path = "/"
+ u.RawQuery = q.Encode()
+ return u
+ }
+
+ switch godocURL.Path {
+ case "/-/go":
+ u.Path = "/std"
+ q.Add("tab", "packages")
+ case "/-/about":
+ u.Path = "/about"
+ case "/":
+ if qparam := godocURL.Query().Get("q"); qparam != "" {
+ u.Path = "/search"
+ q.Set("q", qparam)
+ } else {
+ u.Path = "/"
+ }
+ default:
+ {
+ u.Path = godocURL.Path
+ if _, ok := godocURL.Query()["imports"]; ok {
+ q.Set("tab", "imports")
+ } else if _, ok := godocURL.Query()["importers"]; ok {
+ q.Set("tab", "importedby")
+ } else {
+ q.Set("tab", "doc")
+ }
+ }
+ }
+
+ u.RawQuery = q.Encode()
+ return u
+}
diff --git a/gddo-server/pkgsite_test.go b/gddo-server/pkgsite_test.go
new file mode 100644
index 0000000..ba2394d
--- /dev/null
+++ b/gddo-server/pkgsite_test.go
@@ -0,0 +1,431 @@
+// Copyright 2020 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.
+
+package main
+
+import (
+ "bufio"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+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
+ cookie *http.Cookie
+ }{
+ {
+ name: "test pkggodev-redirect param is on",
+ url: "http://godoc.org/net/http?redirect=on",
+ wantLocationHeader: "https://pkg.go.dev/net/http?tab=doc&utm_source=godoc",
+ wantSetCookieHeader: "pkggodev-redirect=on; Path=/",
+ wantStatusCode: http.StatusFound,
+ },
+ {
+ name: "test pkggodev-redirect param is off",
+ url: "http://godoc.org/net/http?redirect=off",
+ wantLocationHeader: "",
+ wantSetCookieHeader: "pkggodev-redirect=; Path=/; Max-Age=0",
+ wantStatusCode: http.StatusOK,
+ },
+ {
+ name: "test pkggodev-redirect param is unset",
+ url: "http://godoc.org/net/http",
+ wantLocationHeader: "",
+ wantSetCookieHeader: "",
+ wantStatusCode: http.StatusOK,
+ },
+ {
+ name: "toggle enabled pkggodev-redirect cookie",
+ url: "http://godoc.org/net/http?redirect=off",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "true"},
+ wantLocationHeader: "",
+ wantSetCookieHeader: "pkggodev-redirect=; Path=/; Max-Age=0",
+ wantStatusCode: http.StatusOK,
+ },
+ {
+ name: "pkggodev-redirect enabled cookie should redirect",
+ url: "http://godoc.org/net/http",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
+ wantLocationHeader: "https://pkg.go.dev/net/http?tab=doc&utm_source=godoc",
+ wantSetCookieHeader: "",
+ wantStatusCode: http.StatusFound,
+ },
+ {
+ name: "do not redirect if user is returning from pkg.go.dev",
+ url: "http://godoc.org/net/http?utm_source=backtogodoc",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
+ wantStatusCode: http.StatusOK,
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ req := httptest.NewRequest("GET", test.url, nil)
+ if test.cookie != nil {
+ req.AddCookie(test.cookie)
+ }
+
+ w := httptest.NewRecorder()
+ err := handler(w, req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ resp := w.Result()
+
+ 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)
+ }
+ })
+ }
+}
+
+func TestGodoc(t *testing.T) {
+ testCases := []struct {
+ from, to string
+ }{
+ {
+ from: "https://godoc.org/-/about",
+ to: "https://pkg.go.dev/about?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/-/go",
+ to: "https://pkg.go.dev/std?tab=packages&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/?q=foo",
+ to: "https://pkg.go.dev/search?q=foo&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage",
+ to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=doc&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage?imports",
+ to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=imports&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/cloud.google.com/go/storage?importers",
+ to: "https://pkg.go.dev/cloud.google.com/go/storage?tab=importedby&utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/golang.org/x/vgo/vendor/cmd/go/internal/modfile",
+ to: "https://pkg.go.dev/?utm_source=godoc",
+ },
+ {
+ from: "https://godoc.org/golang.org/x/vgo/vendor",
+ to: "https://pkg.go.dev/?utm_source=godoc",
+ },
+ }
+
+ for _, tc := range testCases {
+ u, err := url.Parse(tc.from)
+ if err != nil {
+ t.Errorf("url.Parse(%q): %v", tc.from, err)
+ continue
+ }
+ to := pkgGoDevURL(u)
+ if got, want := to.String(), tc.to; got != want {
+ t.Errorf("pkgGoDevURL(%q) = %q; want %q", u, got, want)
+ }
+ }
+}
+
+func TestNewGDDOEvent(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ url string
+ cookie *http.Cookie
+ want *gddoEvent
+ }{
+ {
+ name: "home page request",
+ url: "https://godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "home page request with cookie on should redirect",
+ url: "https://godoc.org",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "",
+ UsePkgGoDev: true,
+ },
+ },
+ {
+ name: "about page request",
+ url: "https://godoc.org/-/about",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/-/about",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "request with search query parameter",
+ url: "https://godoc.org/?q=test",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "package page request",
+ url: "https://godoc.org/net/http",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "package page request with wrong cookie on should not redirect",
+ url: "https://godoc.org/net/http",
+ cookie: &http.Cookie{Name: "bogus-cookie", Value: "on"},
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "package page request with query parameter off should not redirect",
+ url: "https://godoc.org/net/http?redirect=off",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "package page request with cookie on should redirect",
+ url: "https://godoc.org/net/http",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ UsePkgGoDev: true,
+ },
+ },
+ {
+ name: "package page request with query parameter on should redirect",
+ url: "https://godoc.org/net/http?redirect=on",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: ""},
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ UsePkgGoDev: true,
+ },
+ },
+ {
+ name: "api request",
+ url: "https://api.godoc.org/imports/net/http",
+ want: &gddoEvent{
+ Host: "api.godoc.org",
+ Path: "/imports/net/http",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "api requests should never redirect, even with cookie on",
+ url: "https://api.godoc.org/imports/net/http",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
+ want: &gddoEvent{
+ Host: "api.godoc.org",
+ Path: "/imports/net/http",
+ UsePkgGoDev: false,
+ },
+ },
+ {
+ name: "api requests should never redirect, even with query parameter on",
+ url: "https://api.godoc.org/imports/net/http?redirect=on",
+ cookie: &http.Cookie{Name: "pkggodev-redirect", Value: ""},
+ want: &gddoEvent{
+ Host: "api.godoc.org",
+ Path: "/imports/net/http",
+ UsePkgGoDev: false,
+ },
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ want := test.want
+ want.Latency = 100
+ want.URL = test.url
+ want.Header = http.Header{}
+ want.IsRobot = true
+ r := httptest.NewRequest("GET", test.url, nil)
+ if test.cookie != nil {
+ r.AddCookie(test.cookie)
+ want.Header.Add("Cookie", test.cookie.String())
+ }
+ got := newGDDOEvent(r, want.Latency, want.IsRobot, http.StatusOK)
+ want.Status = http.StatusOK
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Fatalf("mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}
+
+func TestNewGDDOEventFromRequests(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ requestURI string
+ host string
+ want *gddoEvent
+ }{
+ {
+ name: "absolute index path",
+ requestURI: "https://godoc.org",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "",
+ URL: "https://godoc.org",
+ },
+ },
+ {
+ name: "absolute index path with trailing slash",
+ requestURI: "https://godoc.org/",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/",
+ URL: "https://godoc.org/",
+ },
+ },
+ {
+ name: "relative index path",
+ requestURI: "/",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/",
+ URL: "https://godoc.org/",
+ },
+ },
+ {
+ name: "absolute about path",
+ requestURI: "https://godoc.org/-/about",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/-/about",
+ URL: "https://godoc.org/-/about",
+ },
+ },
+ {
+ name: "relative about path",
+ requestURI: "/-/about",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/-/about",
+ URL: "https://godoc.org/-/about",
+ },
+ },
+ {
+ name: "absolute package path",
+ requestURI: "https://godoc.org/net/http",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ URL: "https://godoc.org/net/http",
+ },
+ },
+ {
+ name: "relative package path",
+ requestURI: "/net/http",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ URL: "https://godoc.org/net/http",
+ },
+ },
+ {
+ name: "absolute path with query parameters",
+ requestURI: "https://godoc.org/net/http?q=test",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ URL: "https://godoc.org/net/http?q=test",
+ },
+ },
+ {
+ name: "relative path with query parameters",
+ requestURI: "/net/http?q=test",
+ host: "godoc.org",
+ want: &gddoEvent{
+ Host: "godoc.org",
+ Path: "/net/http",
+ URL: "https://godoc.org/net/http?q=test",
+ },
+ },
+ {
+ name: "absolute api path",
+ requestURI: "https://api.godoc.org/imports/net/http",
+ host: "api.godoc.org",
+ want: &gddoEvent{
+ Host: "api.godoc.org",
+ Path: "/imports/net/http",
+ URL: "https://api.godoc.org/imports/net/http",
+ },
+ },
+ {
+ name: "relative api path",
+ requestURI: "/imports/net/http",
+ host: "api.godoc.org",
+ want: &gddoEvent{
+ Host: "api.godoc.org",
+ Path: "/imports/net/http",
+ URL: "https://api.godoc.org/imports/net/http",
+ },
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ want := test.want
+ want.Latency = 100
+ want.Header = http.Header{}
+ want.IsRobot = false
+ requestLine := "GET " + test.requestURI + " HTTP/1.1\r\nHost: " + test.host + "\r\n\r\n"
+ req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(requestLine)))
+ if err != nil {
+ t.Fatal("invalid NewRequest arguments; " + err.Error())
+ }
+ got := newGDDOEvent(req, want.Latency, want.IsRobot, http.StatusOK)
+ want.Status = http.StatusOK
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Fatalf("mismatch (-want +got):\n%s", diff)
+ }
+ })
+ }
+}