cmd/pkgsite: support proxy

If the -proxy flag is provided, the pkgsite command will fetch modules
from the proxy if they can't be found locally.

For golang/go#47780

Change-Id: I02b9fddac9013d9d3859b78e6c8887282469c9a8
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/345271
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/cmd/pkgsite/main.go b/cmd/pkgsite/main.go
index 95895fa..31f1102 100644
--- a/cmd/pkgsite/main.go
+++ b/cmd/pkgsite/main.go
@@ -37,6 +37,7 @@
 	"golang.org/x/pkgsite/internal/frontend"
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/middleware"
+	"golang.org/x/pkgsite/internal/proxy"
 	"golang.org/x/pkgsite/internal/source"
 )
 
@@ -46,6 +47,7 @@
 	_          = flag.String("static", "static", "path to folder containing static files served")
 	gopathMode = flag.Bool("gopath_mode", false, "assume that local modules' paths are relative to GOPATH/src")
 	httpAddr   = flag.String("http", defaultAddr, "HTTP service address to listen for incoming requests on")
+	useProxy   = flag.Bool("proxy", false, "fetch from GOPROXY if not found locally")
 )
 
 func main() {
@@ -62,15 +64,33 @@
 		paths = []string{"."}
 	}
 
-	server, err := newServer(ctx, paths, *gopathMode)
+	var prox *proxy.Client
+	if *useProxy {
+		fmt.Fprintf(os.Stderr, "BYPASSING LICENSE CHECKING: MAY DISPLAY NON-REDISTRIBUTABLE INFORMATION\n")
+		url := os.Getenv("GOPROXY")
+		if url == "" {
+			die("GOPROXY environment variable is not set")
+		}
+		var err error
+		prox, err = proxy.New(url)
+		if err != nil {
+			die("connecting to proxy: %s", err)
+		}
+	}
+	server, err := newServer(ctx, paths, *gopathMode, prox)
 	if err != nil {
-		log.Fatalf(ctx, "newServer: %v", err)
+		die("%s", err)
 	}
 	router := dcensus.NewRouter(frontend.TagRoute)
 	server.Install(router.Handle, nil, nil)
 	mw := middleware.Timeout(54 * time.Second)
 	log.Infof(ctx, "Listening on addr %s", *httpAddr)
-	log.Fatal(ctx, http.ListenAndServe(*httpAddr, mw(router)))
+	die("%v", http.ListenAndServe(*httpAddr, mw(router)))
+}
+
+func die(format string, args ...interface{}) {
+	fmt.Fprintf(os.Stderr, format, args...)
+	os.Exit(1)
 }
 
 func collectPaths(args []string) []string {
@@ -81,12 +101,16 @@
 	return paths
 }
 
-func newServer(ctx context.Context, paths []string, gopathMode bool) (*frontend.Server, error) {
+func newServer(ctx context.Context, paths []string, gopathMode bool, prox *proxy.Client) (*frontend.Server, error) {
 	getters := buildGetters(ctx, paths, gopathMode)
+	if prox != nil {
+		getters = append(getters, fetch.NewProxyModuleGetter(prox))
+	}
 	lds := fetchdatasource.Options{
-		Getters:            getters,
-		SourceClient:       source.NewClient(time.Second),
-		BypassLicenseCheck: true,
+		Getters:              getters,
+		SourceClient:         source.NewClient(time.Second),
+		ProxyClientForLatest: prox,
+		BypassLicenseCheck:   true,
 	}.New()
 	server, err := frontend.NewServer(frontend.ServerConfig{
 		DataSourceGetter: func(context.Context) internal.DataSource { return lds },
diff --git a/cmd/pkgsite/main_test.go b/cmd/pkgsite/main_test.go
index b749076..3a90036 100644
--- a/cmd/pkgsite/main_test.go
+++ b/cmd/pkgsite/main_test.go
@@ -9,14 +9,22 @@
 	"flag"
 	"net/http"
 	"net/http/httptest"
+	"path/filepath"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
+	"golang.org/x/pkgsite/internal/proxy/proxytest"
 )
 
 func Test(t *testing.T) {
-	flag.Set("static", "../../static")
-	server, err := newServer(context.Background(), []string{"../../internal/fetch/testdata/has_go_mod"}, false)
+	repoPath := func(fn string) string { return filepath.Join("..", "..", fn) }
+	localModule := repoPath("internal/fetch/testdata/has_go_mod")
+	flag.Set("static", repoPath("static"))
+	testModules := proxytest.LoadTestModules(repoPath("internal/proxy/testdata"))
+	prox, teardown := proxytest.SetupTestClient(t, testModules)
+	defer teardown()
+
+	server, err := newServer(context.Background(), []string{localModule}, false, prox)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -24,9 +32,11 @@
 	server.Install(mux.Handle, nil, nil)
 	w := httptest.NewRecorder()
 
-	mux.ServeHTTP(w, httptest.NewRequest("GET", "/example.com/testmod", nil))
-	if w.Code != http.StatusOK {
-		t.Errorf("%q: got status code = %d, want %d", "/testmod", w.Code, http.StatusOK)
+	for _, url := range []string{"/example.com/testmod", "/example.com/single/pkg"} {
+		mux.ServeHTTP(w, httptest.NewRequest("GET", url, nil))
+		if w.Code != http.StatusOK {
+			t.Errorf("%q: got status code = %d, want %d", url, w.Code, http.StatusOK)
+		}
 	}
 }