Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 66 additions & 50 deletions internal/handler/download_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,8 +758,8 @@ func TestMavenHandler_MetadataProxied(t *testing.T) {

paths := []string{
"/com/google/guava/guava/maven-metadata.xml",
"/com/google/guava/guava/32.1.3-jre/guava-32.1.3-jre.jar.sha1",
"/com/google/guava/guava/32.1.3-jre/guava-32.1.3-jre.jar.md5",
"/com/google/guava/guava/maven-metadata.xml.sha1",
"/com/google/guava/guava/1.0-SNAPSHOT/maven-metadata.xml",
}

for _, path := range paths {
Expand Down Expand Up @@ -829,6 +829,47 @@ func TestMavenHandler_ArtifactExtensions(t *testing.T) {
}
}

func TestMavenHandler_ChecksumsCachedAsArtifacts(t *testing.T) {
proxy, _, _, fetcher := setupTestProxy(t)

upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Errorf("checksum %s proxied as metadata", r.URL.Path)
}))
defer upstream.Close()

h := NewMavenHandler(proxy, "http://localhost", "", "")
h.upstreamURL = upstream.URL
proxy.HTTPClient = upstream.Client()

srv := httptest.NewServer(h.Routes())
defer srv.Close()

for _, name := range []string{"lib-1.0.jar.sha1", "lib-1.0.pom.md5", "lib-1.0.jar.asc", "lib-1.0-bin.tar.gz.sha512"} {
path := "/com/example/lib/1.0/" + name
for i := range 2 {
fetcher.artifact = &fetch.Artifact{
Body: io.NopCloser(strings.NewReader("checksum")),
ContentType: "text/plain",
}
fetcher.fetchCalled = false

resp, err := http.Get(srv.URL + path)
if err != nil {
t.Fatalf("GET %s failed: %v", path, err)
}
body, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()

if resp.StatusCode != http.StatusOK || string(body) != "checksum" {
t.Errorf("GET %s: status %d body %q", path, resp.StatusCode, body)
}
if fetcher.fetchCalled != (i == 0) {
t.Errorf("GET %s request %d: fetched = %v, want %v", path, i+1, fetcher.fetchCalled, i == 0)
}
}
}
}

func TestMavenHandler_CacheMiss(t *testing.T) {
proxy, _, _, fetcher := setupTestProxy(t)
fetcher.artifact = &fetch.Artifact{
Expand Down Expand Up @@ -929,10 +970,8 @@ func TestMavenHandler_GradlePluginMarkerFallbackAndCache(t *testing.T) {

func TestMavenHandler_GradlePluginMarkerMetadataFallback(t *testing.T) {
paths := map[string]string{
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/8.4.0/com.diffplug.spotless.gradle.plugin-8.4.0.pom.sha1": "sha1",
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/8.4.0/com.diffplug.spotless.gradle.plugin-8.4.0.pom.sha256": "sha256",
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/8.4.0/com.diffplug.spotless.gradle.plugin-8.4.0.pom.md5": "md5",
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/maven-metadata.xml": "<metadata/>",
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/maven-metadata.xml": "<metadata/>",
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/maven-metadata.xml.sha1": "sha1",
}

primaryHits := map[string]int{}
Expand Down Expand Up @@ -990,63 +1029,40 @@ func TestMavenHandler_GradlePluginMarkerMetadataFallback(t *testing.T) {
}
}

func TestMavenHandler_GradlePluginImplementationMetadataFallback(t *testing.T) {
paths := map[string]string{
"/com/diffplug/spotless/spotless-plugin-gradle/8.4.0/spotless-plugin-gradle-8.4.0.jar.sha1": "impl-sha1",
"/com/diffplug/spotless/spotless-plugin-gradle/8.4.0/spotless-plugin-gradle-8.4.0.jar.sha256": "impl-sha256",
func TestMavenHandler_GradlePluginChecksumFallbackToPluginPortal(t *testing.T) {
primaryUpstream := "https://repo1.maven.org/maven2"
pluginPortalUpstream := "https://plugins.gradle.org/m2"
paths := []string{
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/8.4.0/com.diffplug.spotless.gradle.plugin-8.4.0.pom.sha1",
"/com/diffplug/spotless/com.diffplug.spotless.gradle.plugin/8.4.0/com.diffplug.spotless.gradle.plugin-8.4.0.pom.md5",
"/com/diffplug/spotless/spotless-plugin-gradle/8.4.0/spotless-plugin-gradle-8.4.0.jar.sha1",
"/com/diffplug/spotless/spotless-plugin-gradle/8.4.0/spotless-plugin-gradle-8.4.0.jar.sha256",
}

primaryHits := map[string]int{}
pluginHits := map[string]int{}

primary := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
primaryHits[r.URL.Path]++
if _, ok := paths[r.URL.Path]; ok {
http.NotFound(w, r)
return
}
t.Fatalf("unexpected path to primary upstream: %s", r.URL.Path)
}))
defer primary.Close()

pluginPortal := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pluginHits[r.URL.Path]++
body, ok := paths[r.URL.Path]
if !ok {
http.NotFound(w, r)
return
for _, reqPath := range paths {
proxy, _, _, fetcher := setupTestProxy(t)
fetcher.fetchErrByURL = map[string]error{primaryUpstream + reqPath: ErrUpstreamNotFound}
fetcher.artifact = &fetch.Artifact{
Body: io.NopCloser(strings.NewReader("checksum")),
ContentType: "text/plain",
}
_, _ = io.WriteString(w, body)
}))
defer pluginPortal.Close()

proxy, _, _, _ := setupTestProxy(t)
proxy.HTTPClient = primary.Client()

h := NewMavenHandler(proxy, "http://localhost", primary.URL, pluginPortal.URL)
srv := httptest.NewServer(h.Routes())
defer srv.Close()
h := NewMavenHandler(proxy, "http://localhost", primaryUpstream, pluginPortalUpstream)
srv := httptest.NewServer(h.Routes())

for reqPath, wantBody := range paths {
resp, err := http.Get(srv.URL + reqPath)
if err != nil {
t.Fatalf("GET %s failed: %v", reqPath, err)
}
body, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
srv.Close()

if resp.StatusCode != http.StatusOK {
t.Fatalf("GET %s: status = %d, want %d", reqPath, resp.StatusCode, http.StatusOK)
}
if string(body) != wantBody {
t.Fatalf("GET %s: body = %q, want %q", reqPath, body, wantBody)
}

if primaryHits[reqPath] == 0 {
t.Fatalf("GET %s did not hit primary upstream", reqPath)
if resp.StatusCode != http.StatusOK || string(body) != "checksum" {
t.Fatalf("GET %s: status %d body %q", reqPath, resp.StatusCode, body)
}
if pluginHits[reqPath] == 0 {
t.Fatalf("GET %s did not hit plugin portal fallback", reqPath)
if want := pluginPortalUpstream + reqPath; fetcher.fetchedURL != want {
t.Fatalf("GET %s: fetched URL = %q, want %q", reqPath, fetcher.fetchedURL, want)
}
}
}
Expand Down
33 changes: 20 additions & 13 deletions internal/handler/maven.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,19 @@ func (h *MavenHandler) handleRequest(w http.ResponseWriter, r *http.Request) {
return
}

// Check if this is an artifact file or metadata
filename := path.Base(urlPath)

if h.isMetadataFile(filename) {
if h.isVersionListingFile(filename) {
h.handleMetadata(w, r, urlPath)
return
}

if h.isArtifactFile(filename) {
// Cache artifact files
if h.isArtifactFile(filename) || h.isChecksumFile(filename) {
h.handleDownload(w, r, urlPath)
return
}

// Proxy everything else (directory listings, checksums, etc.)
// Proxy everything else (directory listings etc.)
h.proxyUpstream(w, r)
}

Expand Down Expand Up @@ -192,14 +190,23 @@ func (h *MavenHandler) isArtifactFile(filename string) bool {
return false
}

// isMetadataFile returns true if the filename is Maven metadata.
func (h *MavenHandler) isMetadataFile(filename string) bool {
return filename == "maven-metadata.xml" ||
strings.HasSuffix(filename, ".sha1") ||
strings.HasSuffix(filename, ".sha256") ||
strings.HasSuffix(filename, ".sha512") ||
strings.HasSuffix(filename, ".md5") ||
strings.HasSuffix(filename, ".asc")
// isVersionListingFile matches maven-metadata.xml and its checksums. The
// listing changes with every release, so it is cached for the metadata TTL
// rather than kept like an artifact.
func (h *MavenHandler) isVersionListingFile(filename string) bool {
return strings.HasPrefix(filename, "maven-metadata.xml")
}

// isChecksumFile matches the checksum and signature files published beside
// an artifact. They are as immutable as the artifact, so they are cached
// with it rather than refetched on every request.
func (h *MavenHandler) isChecksumFile(filename string) bool {
for _, ext := range []string{".sha1", ".sha256", ".sha512", ".md5", ".asc"} {
if strings.HasSuffix(filename, ext) {
return true
}
}
return false
}

// proxyUpstream forwards a request to Maven Central without caching.
Expand Down
83 changes: 35 additions & 48 deletions internal/handler/maven_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,62 +71,49 @@ func TestMavenIsArtifactFile(t *testing.T) {
}
}

func TestMavenIsMetadataFile(t *testing.T) {
func TestMavenIsVersionListingFile(t *testing.T) {
h := &MavenHandler{}

tests := []struct {
name string
filename string
want bool
}{
{
name: "pom is artifact, not metadata",
filename: "com.diffplug.spotless.gradle.plugin-8.4.0.pom",
want: false,
},
{
name: "pom checksum is metadata",
filename: "com.diffplug.spotless.gradle.plugin-8.4.0.pom.sha1",
want: true,
},
{
name: "metadata file",
filename: "maven-metadata.xml",
want: true,
},
{
name: "metadata checksum",
filename: "maven-metadata.xml.sha256",
want: true,
},
{
name: "jar checksum is metadata",
filename: "guava-32.1.3-jre.jar.sha1",
want: true,
},
{
name: "asc signature is metadata",
filename: "guava-32.1.3-jre.jar.asc",
want: true,
},
{
name: "regular jar is not metadata",
filename: "guava-32.1.3-jre.jar",
want: false,
},
{
name: "pom checksum is metadata",
filename: "guava-32.1.3-jre.pom.sha1",
want: true,
},
{"maven-metadata.xml", true},
{"maven-metadata.xml.sha1", true},
{"maven-metadata.xml.sha256", true},
{"guava-32.1.3-jre.jar", false},
{"guava-32.1.3-jre.jar.sha1", false},
{"com.diffplug.spotless.gradle.plugin-8.4.0.pom", false},
}

for _, tt := range tests {
if got := h.isVersionListingFile(tt.filename); got != tt.want {
t.Errorf("isVersionListingFile(%q) = %v, want %v", tt.filename, got, tt.want)
}
}
}

func TestMavenIsChecksumFile(t *testing.T) {
h := &MavenHandler{}

tests := []struct {
filename string
want bool
}{
{"guava-32.1.3-jre.jar.sha1", true},
{"guava-32.1.3-jre.jar.md5", true},
{"guava-32.1.3-jre.pom.sha1", true},
{"guava-32.1.3-jre.jar.asc", true},
{"apache-maven-3.9.9-bin.tar.gz.sha512", true},
{"com.diffplug.spotless.gradle.plugin-8.4.0.pom.sha1", true},
{"guava-32.1.3-jre.jar", false},
{"guava-32.1.3-jre.pom", false},
{"maven-metadata.xml", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := h.isMetadataFile(tt.filename)
if got != tt.want {
t.Errorf("isMetadataFile(%q) = %v, want %v", tt.filename, got, tt.want)
}
})
if got := h.isChecksumFile(tt.filename); got != tt.want {
t.Errorf("isChecksumFile(%q) = %v, want %v", tt.filename, got, tt.want)
}
}
}
Loading