internal/proxy: proxy that disables fetch Add a server that proxies requests to proxy.golang.org but disables fetches. We will use this so that `go mod tidy` doesn't download modules that aren't in the proxy's cache. Change-Id: I02886681e3eb267913421ac995a6894892cc9e7b Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/493675 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Jonathan Amsterdam <jba@google.com> Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/internal/proxy/server.go b/internal/proxy/server.go new file mode 100644 index 0000000..3d6fc4a --- /dev/null +++ b/internal/proxy/server.go
@@ -0,0 +1,46 @@ +// Copyright 2023 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. + +package proxy + +import ( + "io" + "net/http" + "net/http/httptest" + + "golang.org/x/pkgsite-metrics/internal/log" +) + +// ServeDisablingFetch returns a server that proxies requests to proxy.golang.org, +// adding the Disable-Fetch header to prevent the proxy from fetching old modules. +func ServeDisablingFetch() *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + resp, err := proxyRequest(r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for k, vs := range resp.Header { + for _, v := range vs { + w.Header().Add(k, v) + } + } + w.WriteHeader(resp.StatusCode) + defer resp.Body.Close() + _, err = io.Copy(w, resp.Body) + if err != nil { + log.Errorf(r.Context(), err, "ServeDisablingFetch: io.Copy") + } + })) +} + +func proxyRequest(r *http.Request) (*http.Response, error) { + url := "https://proxy.golang.org" + r.URL.Path + req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Set(DisableFetchHeader, "true") + return http.DefaultClient.Do(req) +}
diff --git a/internal/proxy/server_test.go b/internal/proxy/server_test.go new file mode 100644 index 0000000..edf6248 --- /dev/null +++ b/internal/proxy/server_test.go
@@ -0,0 +1,55 @@ +// Copyright 2023 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. + +package proxy + +import ( + "bytes" + "io" + "net/http" + "net/url" + "testing" + + test "golang.org/x/pkgsite-metrics/internal/testing" +) + +// This module is currently not stored on the proxy. +const missingModule = "code.haiziwang.com/golang/autil" + +func TestServer(t *testing.T) { + test.NeedsIntegrationEnv(t) + s := ServeDisablingFetch() + defer s.Close() + serverURL, err := url.Parse(s.URL) + if err != nil { + t.Fatal(err) + } + + url := serverURL.JoinPath("github.com/pkg/errors/@v/list") + resp, err := http.Get(url.String()) + if err != nil { + t.Fatal(err) + } + if g, w := resp.StatusCode, 200; g != w { + t.Fatalf("status: got %d, want %d", g, w) + } + defer resp.Body.Close() + got, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + want := "v0.8.1\n" + if !bytes.Contains(got, []byte(want)) { + t.Errorf("got body %q, missing %q", got, want) + } + + url = serverURL.JoinPath(missingModule, "/@v/list") + resp, err = http.Get(url.String()) + if err != nil { + t.Fatal(err) + } + if g, w := resp.StatusCode, http.StatusNotFound; g != w { + t.Fatalf("status: got %d, want %d", g, w) + } +}