gddo-server: parse host and url from request correctly

Most browsers send requests to http://godoc.org/net/http as

GET /net/http HTTP/1.1
Host: godoc.org

instead of as expected in current tests

GET https://godoc.org/net/http HTTP/1.1
Host: godoc.org

This causes http.Request.URL to have empty strings for the "Scheme" and
"Host" fields (see https://golang.org/src/net/http/request.go#L120 and
RFC 7230 Section 5.3). As a result, server logs show most gddoEvent
structs as having an empty "Host" field and a relative path for the URL.

This CL enables the parsing of these fields from the other fields of the
http.Request struct.

Change-Id: Iab923a0d9a70f32f36b6e4a15019afc648699796
Reviewed-on: https://go-review.googlesource.com/c/gddo/+/236143
Reviewed-by: 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 d316344..9ffb342 100644
--- a/gddo-server/main.go
+++ b/gddo-server/main.go
@@ -1074,11 +1074,20 @@
 }
 
 func newGDDOEvent(r *http.Request, latency time.Duration, isRobot bool) *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
+	}
 	pkgGoDevURL := url.URL{Scheme: "https", Host: pkgGoDevHost}
 	return &gddoEvent{
-		Host:         r.URL.Host,
+		Host:         targetURL.Host,
 		Path:         r.URL.Path,
-		URL:          r.URL.String(),
+		URL:          targetURL.String(),
 		Header:       r.Header,
 		RedirectHost: pkgGoDevURL.String(),
 		Latency:      latency,
diff --git a/gddo-server/main_test.go b/gddo-server/main_test.go
index 1f29fa7..28f53a4 100644
--- a/gddo-server/main_test.go
+++ b/gddo-server/main_test.go
@@ -7,9 +7,11 @@
 package main
 
 import (
+	"bufio"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -306,3 +308,140 @@
 		})
 	}
 }
+
+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.RedirectHost = "https://" + pkgGoDevHost
+			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)
+			if diff := cmp.Diff(want, got); diff != "" {
+				t.Fatalf("mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}