1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
// SPDX-License-Identifier: Apache-2.0
package benchmark_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
"go.xyrillian.de/gg/assert"
pr "go.xyrillian.de/gg/pathrouter"
)
func BenchmarkRouterWithDistributionAPI(b *testing.B) {
// This benchmark uses the OCI Distribution API as a moderately complex
// real-world example of an HTTP API with parametrized endpoint paths.
testWith := func(h http.Handler) func(b *testing.B) {
return func(b *testing.B) {
for b.Loop() {
req := httptest.NewRequest(http.MethodGet, "/v2/foo/bar/manifests/latest", http.NoBody)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
assert.Equal(b, rec.Code, http.StatusForbidden)
assert.Equal(b, rec.Body.String(), "GetOrHeadManifest is forbidden\n")
if b.Failed() {
b.FailNow()
}
}
}
}
b.Run("platform=gorilla-mux", testWith(buildMuxRouterForDistributionAPI()))
b.Run("platform=gg-pathrouter", testWith(buildPathrouterForDistributionAPI()))
}
func buildMuxRouterForDistributionAPI() http.Handler {
handle := func(endpointName string) http.HandlerFunc {
msg := endpointName + " is forbidden"
return func(w http.ResponseWriter, r *http.Request) {
http.Error(w, msg, http.StatusForbidden)
}
}
r := mux.NewRouter()
r.Methods("GET").Path("/v2/").HandlerFunc(handle("GetToplevel"))
r.Methods("GET").Path("/v2/_catalog").HandlerFunc(handle("GetCatalog"))
r.Methods("DELETE").Path("/v2/{repository:.+}/blobs/{digest}").HandlerFunc(handle("DeleteBlob"))
r.Methods("GET", "HEAD").Path("/v2/{repository:.+}/blobs/{digest}").HandlerFunc(handle("GetOrHeadBlob"))
r.Methods("POST").Path("/v2/{repository:.+}/blobs/uploads/").HandlerFunc(handle("StartBlobUpload"))
r.Methods("DELETE").Path("/v2/{repository:.+}/blobs/uploads/{uuid}").HandlerFunc(handle("DeleteBlobUpload"))
r.Methods("GET").Path("/v2/{repository:.+}/blobs/uploads/{uuid}").HandlerFunc(handle("GetBlobUpload"))
r.Methods("PATCH").Path("/v2/{repository:.+}/blobs/uploads/{uuid}").HandlerFunc(handle("ContinueBlobUpload"))
r.Methods("PUT").Path("/v2/{repository:.+}/blobs/uploads/{uuid}").HandlerFunc(handle("FinishBlobUpload"))
r.Methods("DELETE").Path("/v2/{repository:.+}/manifests/{reference}").HandlerFunc(handle("DeleteManifest"))
r.Methods("GET", "HEAD").Path("/v2/{repository:.+}/manifests/{reference}").HandlerFunc(handle("GetOrHeadManifest"))
r.Methods("PUT").Path("/v2/{repository:.+}/manifests/{reference}").HandlerFunc(handle("PutManifest"))
r.Methods("GET").Path("/v2/{repository:.+}/referrers/{reference}").HandlerFunc(handle("GetReferrers"))
r.Methods("GET").Path("/v2/{repository:.+}/tags/list").HandlerFunc(handle("ListTags"))
return r
}
func buildPathrouterForDistributionAPI() http.Handler {
handle := func(endpointName string) pr.HandlerFunc {
msg := endpointName + " is forbidden"
return func(w http.ResponseWriter, r *http.Request, vars map[string]string) {
http.Error(w, msg, http.StatusForbidden)
}
}
return pr.Choice(
pr.Element("v2", pr.Choice(
pr.Element("/", pr.Handlers(pr.ByMethod{
http.MethodGet: handle("GetToplevel"),
http.MethodHead: nil,
})),
pr.Element("_catalog", pr.Handlers(pr.ByMethod{
http.MethodGet: handle("GetCatalog"),
http.MethodHead: nil,
})),
pr.CatchAllVariable("repository", pr.Choice(
pr.Element("blobs", pr.Choice(
pr.Variable("digest", pr.Handlers(pr.ByMethod{
http.MethodDelete: handle("DeleteBlob"),
http.MethodGet: handle("GetOrHeadBlob"),
})),
pr.Element("uploads", pr.Choice(
pr.Element("/", pr.Handlers(pr.ByMethod{
http.MethodPost: handle("StartBlobUpload"),
})),
pr.Variable("uuid", pr.Handlers(pr.ByMethod{
http.MethodDelete: handle("DeleteBlobUpload"),
http.MethodGet: handle("GetBlobUpload"),
http.MethodHead: nil,
http.MethodPatch: handle("ContinueBlobUpload"),
http.MethodPut: handle("FinishBlobUpload"),
})),
)),
)),
pr.Element("manifests", pr.Variable("reference", pr.Handlers(pr.ByMethod{
http.MethodDelete: handle("DeleteManifest"),
http.MethodGet: handle("GetOrHeadManifest"),
http.MethodPut: handle("PutManifest"),
}))),
pr.Element("referrers", pr.Variable("reference", pr.Handlers(pr.ByMethod{
http.MethodGet: handle("GetReferrers"),
http.MethodHead: nil,
}))),
pr.Element("tags", pr.Element("list", pr.Handlers(pr.ByMethod{
http.MethodGet: handle("ListTags"),
http.MethodHead: nil,
}))),
)),
)),
)
}
|