summaryrefslogtreecommitdiff
path: root/benchmark/pathrouter_test.go
blob: 361cc2faae609015e0fac9c7d1bbea27d2490319 (plain)
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,
				}))),
			)),
		)),
	)
}