gddo-server: tee requests directly to pkg.go.dev

Change-Id: Ibe8c66efd7771421709695e94a94eb5e945ba5f4
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/274860
Trust: 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 99e98a7..f34fc0f 100644
--- a/gddo-server/main.go
+++ b/gddo-server/main.go
@@ -1046,7 +1046,7 @@
 	if f, ok := w.(http.Flusher); ok {
 		f.Flush()
 	}
-	makePkgGoDevRequest(r, latency, s.isRobot(r), translateStatus(w2.status))
+	s.teeRequestToPkgGoDev(r, latency, translateStatus(w2.status))
 }
 
 func (s *server) logRequestStart(req *http.Request) {
@@ -1074,6 +1074,39 @@
 	})
 }
 
+func (s *server) teeRequestToPkgGoDev(r *http.Request, latency time.Duration, status int) {
+	if !shouldTeeRequest(r.URL.Path) {
+		log.Printf("teeRequestToPkgGoDev(%q): not teeing request", r.URL.Path)
+		return
+	}
+	if os.Getenv("GDDO_TEE_REQUESTS_TO_PKGGODEV") == "true" {
+		gddoEvent, pkggodevEvent := teeRequestToPkgGoDev(r, latency, s.isRobot(r), status)
+		payload := map[string]interface{}{
+			"godoc.org":  gddoEvent,
+			"pkg.go.dev": pkggodevEvent,
+		}
+
+		if s.gceLogger == nil {
+			for k, v := range payload {
+				log.Printf("%q", k)
+				log.Printf("%+v", v)
+			}
+			return
+		}
+		s.gceLogger.Log(logging.Entry{
+			HTTPRequest: &logging.HTTPRequest{
+				Request: r,
+				Latency: latency,
+				Status:  status,
+			},
+			Payload:  payload,
+			Severity: logging.Info,
+		})
+		return
+	}
+	teeRequestToTeeproxy(r, latency, s.isRobot(r), status)
+}
+
 func main() {
 	ctx := context.Background()
 	v, err := loadConfig(ctx, os.Args)
diff --git a/gddo-server/pkgsite.go b/gddo-server/pkgsite.go
index b14e320..f209f37 100644
--- a/gddo-server/pkgsite.go
+++ b/gddo-server/pkgsite.go
@@ -16,27 +16,70 @@
 	"path/filepath"
 	"strings"
 	"time"
+
+	"golang.org/x/net/context/ctxhttp"
 )
 
-// makePkgGoDevRequest makes a request to the teeproxy with data about the
+type pkggodevEvent struct {
+	Host    string
+	Path    string
+	Status  int
+	URL     string
+	Latency time.Duration
+	Error   error
+}
+
+func teeRequestToPkgGoDev(godocReq *http.Request, latency time.Duration, isRobot bool, status int) (gddoEvent *gddoEvent, pkgEvent *pkggodevEvent) {
+	gddoEvent = newGDDOEvent(godocReq, latency, isRobot, status)
+	u := pkgGoDevURL(godocReq.URL)
+
+	// Strip the utm_source from the URL.
+	vals := u.Query()
+	vals.Del("utm_source")
+	u.RawQuery = vals.Encode()
+
+	pkgEvent = &pkggodevEvent{
+		Host: u.Host,
+		Path: u.Path,
+		URL:  u.String(),
+	}
+	req, err := http.NewRequest("GET", u.String(), nil)
+	if err != nil {
+		pkgEvent.Status = http.StatusInternalServerError
+		pkgEvent.Error = err
+		return gddoEvent, pkgEvent
+	}
+	start := time.Now()
+	xfwd := req.Header.Get("X-Forwarded-for")
+	req.Header.Set("X-Godoc-Forwarded-for", xfwd)
+	resp, err := ctxhttp.Do(godocReq.Context(), http.DefaultClient, req)
+	if err != nil {
+		// Use StatusBadGateway to indicate the upstream error.
+		pkgEvent.Status = http.StatusBadGateway
+		pkgEvent.Error = err
+		return gddoEvent, pkgEvent
+	}
+
+	pkgEvent.Status = resp.StatusCode
+	pkgEvent.Latency = time.Since(start)
+	return gddoEvent, pkgEvent
+
+}
+
+// teeRequestToTeeproxy makes a request to the teeproxy with data about the
 // godoc.org request.
-func makePkgGoDevRequest(r *http.Request, latency time.Duration, isRobot bool, status int) {
+func teeRequestToTeeproxy(r *http.Request, latency time.Duration, isRobot bool, status int) {
 	var msg string
 	defer func() {
-		log.Printf("makePkgGoDevRequest(%q): %s", r.URL.Path, msg)
+		log.Printf("teeRequestToTeeproxy(%q): %s", r.URL.Path, msg)
 	}()
 
-	if !shouldTeeRequest(r.URL.Path) {
-		msg = "not teeing request"
-		return
-	}
 	event := newGDDOEvent(r, latency, isRobot, status)
 	b, err := json.Marshal(event)
 	if err != nil {
 		msg = fmt.Sprintf("json.Marshal(%v): %v", event, err)
 		return
 	}
-
 	teeproxyURL := url.URL{Scheme: "https", Host: teeproxyHost}
 	if _, err := http.Post(teeproxyURL.String(), jsonMIMEType, bytes.NewReader(b)); err != nil {
 		msg = fmt.Sprintf("http.Post(%q, %q, %v): %v", teeproxyURL.String(), jsonMIMEType, event, err)
@@ -59,6 +102,7 @@
 	".js":   true,
 	".txt":  true,
 	".xml":  true,
+	".ico":  true,
 }
 
 // shouldTeeRequest reports whether a request should be teed to pkg.go.dev.
@@ -86,6 +130,7 @@
 	Latency     time.Duration
 	IsRobot     bool
 	UsePkgGoDev bool
+	Error       error
 }
 
 func newGDDOEvent(r *http.Request, latency time.Duration, isRobot bool, status int) *gddoEvent {
diff --git a/gddo-server/pkgsite_test.go b/gddo-server/pkgsite_test.go
index 8515db6..f825f5b 100644
--- a/gddo-server/pkgsite_test.go
+++ b/gddo-server/pkgsite_test.go
@@ -175,12 +175,12 @@
 		name   string
 		url    string
 		cookie *http.Cookie
-		want   *gddoEvent
+		want   *requestEvent
 	}{
 		{
 			name: "home page request",
 			url:  "https://godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "",
 				UsePkgGoDev: false,
@@ -190,7 +190,7 @@
 			name:   "home page request with cookie on should redirect",
 			url:    "https://godoc.org",
 			cookie: &http.Cookie{Name: "pkggodev-redirect", Value: "on"},
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "",
 				UsePkgGoDev: true,
@@ -199,7 +199,7 @@
 		{
 			name: "about page request",
 			url:  "https://godoc.org/-/about",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/-/about",
 				UsePkgGoDev: false,
@@ -208,7 +208,7 @@
 		{
 			name: "request with search query parameter",
 			url:  "https://godoc.org/?q=test",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/",
 				UsePkgGoDev: false,
@@ -217,7 +217,7 @@
 		{
 			name: "package page request",
 			url:  "https://godoc.org/net/http",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/net/http",
 				UsePkgGoDev: false,
@@ -227,7 +227,7 @@
 			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{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/net/http",
 				UsePkgGoDev: false,
@@ -237,7 +237,7 @@
 			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{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/net/http",
 				UsePkgGoDev: false,
@@ -247,7 +247,7 @@
 			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{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/net/http",
 				UsePkgGoDev: true,
@@ -257,7 +257,7 @@
 			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{
+			want: &requestEvent{
 				Host:        "godoc.org",
 				Path:        "/net/http",
 				UsePkgGoDev: true,
@@ -266,7 +266,7 @@
 		{
 			name: "api request",
 			url:  "https://api.godoc.org/imports/net/http",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host:        "api.godoc.org",
 				Path:        "/imports/net/http",
 				UsePkgGoDev: false,
@@ -276,7 +276,7 @@
 			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{
+			want: &requestEvent{
 				Host:        "api.godoc.org",
 				Path:        "/imports/net/http",
 				UsePkgGoDev: false,
@@ -286,7 +286,7 @@
 			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{
+			want: &requestEvent{
 				Host:        "api.godoc.org",
 				Path:        "/imports/net/http",
 				UsePkgGoDev: false,
@@ -318,13 +318,13 @@
 		name       string
 		requestURI string
 		host       string
-		want       *gddoEvent
+		want       *requestEvent
 	}{
 		{
 			name:       "absolute index path",
 			requestURI: "https://godoc.org",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "",
 				URL:  "https://godoc.org",
@@ -334,7 +334,7 @@
 			name:       "absolute index path with trailing slash",
 			requestURI: "https://godoc.org/",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/",
 				URL:  "https://godoc.org/",
@@ -344,7 +344,7 @@
 			name:       "relative index path",
 			requestURI: "/",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/",
 				URL:  "https://godoc.org/",
@@ -354,7 +354,7 @@
 			name:       "absolute about path",
 			requestURI: "https://godoc.org/-/about",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/-/about",
 				URL:  "https://godoc.org/-/about",
@@ -364,7 +364,7 @@
 			name:       "relative about path",
 			requestURI: "/-/about",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/-/about",
 				URL:  "https://godoc.org/-/about",
@@ -374,7 +374,7 @@
 			name:       "absolute package path",
 			requestURI: "https://godoc.org/net/http",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/net/http",
 				URL:  "https://godoc.org/net/http",
@@ -384,7 +384,7 @@
 			name:       "relative package path",
 			requestURI: "/net/http",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/net/http",
 				URL:  "https://godoc.org/net/http",
@@ -394,7 +394,7 @@
 			name:       "absolute path with query parameters",
 			requestURI: "https://godoc.org/net/http?q=test",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/net/http",
 				URL:  "https://godoc.org/net/http?q=test",
@@ -404,7 +404,7 @@
 			name:       "relative path with query parameters",
 			requestURI: "/net/http?q=test",
 			host:       "godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "godoc.org",
 				Path: "/net/http",
 				URL:  "https://godoc.org/net/http?q=test",
@@ -414,7 +414,7 @@
 			name:       "absolute api path",
 			requestURI: "https://api.godoc.org/imports/net/http",
 			host:       "api.godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "api.godoc.org",
 				Path: "/imports/net/http",
 				URL:  "https://api.godoc.org/imports/net/http",
@@ -424,7 +424,7 @@
 			name:       "relative api path",
 			requestURI: "/imports/net/http",
 			host:       "api.godoc.org",
-			want: &gddoEvent{
+			want: &requestEvent{
 				Host: "api.godoc.org",
 				Path: "/imports/net/http",
 				URL:  "https://api.godoc.org/imports/net/http",
diff --git a/go.mod b/go.mod
index 66b1931..97ede31 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@
 	github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1
 	github.com/spf13/viper v1.0.0
 	github.com/stretchr/testify v1.4.0 // indirect
+	golang.org/x/net v0.0.0-20190603091049-60506f45cf65
 	golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2
 	golang.org/x/sync v0.0.0-20170517211232-f52d1811a629 // indirect
 	golang.org/x/time v0.0.0-20170424234030-8be79e1e0910 // indirect