mirror of https://github.com/k3s-io/k3s
Merge pull request #75699 from smarterclayton/fast_encode
Avoid allocations when building SelfLinks and fast path escapek3s-v1.15.3
commit
1514bb2141
|
@ -34,6 +34,7 @@ go_test(
|
|||
"//staging/src/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//vendor/github.com/evanphx/json-patch:go_default_library",
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
"//vendor/k8s.io/utils/trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -86,6 +87,21 @@ func (n ContextBasedNaming) Name(req *http.Request) (namespace, name string, err
|
|||
return ns, requestInfo.Name, nil
|
||||
}
|
||||
|
||||
// fastURLPathEncode encodes the provided path as a URL path
|
||||
func fastURLPathEncode(path string) string {
|
||||
for _, r := range []byte(path) {
|
||||
switch {
|
||||
case r >= '-' && r <= '9', r >= 'A' && r <= 'Z', r >= 'a' && r <= 'z':
|
||||
// characters within this range do not require escaping
|
||||
default:
|
||||
var u url.URL
|
||||
u.Path = path
|
||||
return u.EscapedPath()
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj runtime.Object) (uri string, err error) {
|
||||
namespace, name, err := n.ObjectName(obj)
|
||||
if err == errEmptyName && len(requestInfo.Name) > 0 {
|
||||
|
@ -101,19 +117,23 @@ func (n ContextBasedNaming) GenerateLink(requestInfo *request.RequestInfo, obj r
|
|||
return n.SelfLinkPathPrefix + url.QueryEscape(name) + n.SelfLinkPathSuffix, nil
|
||||
}
|
||||
|
||||
return n.SelfLinkPathPrefix +
|
||||
url.QueryEscape(namespace) +
|
||||
"/" + url.QueryEscape(requestInfo.Resource) + "/" +
|
||||
url.QueryEscape(name) +
|
||||
n.SelfLinkPathSuffix,
|
||||
nil
|
||||
builder := strings.Builder{}
|
||||
builder.Grow(len(n.SelfLinkPathPrefix) + len(namespace) + len(requestInfo.Resource) + len(name) + len(n.SelfLinkPathSuffix) + 8)
|
||||
builder.WriteString(n.SelfLinkPathPrefix)
|
||||
builder.WriteString(namespace)
|
||||
builder.WriteByte('/')
|
||||
builder.WriteString(requestInfo.Resource)
|
||||
builder.WriteByte('/')
|
||||
builder.WriteString(name)
|
||||
builder.WriteString(n.SelfLinkPathSuffix)
|
||||
return fastURLPathEncode(builder.String()), nil
|
||||
}
|
||||
|
||||
func (n ContextBasedNaming) GenerateListLink(req *http.Request) (uri string, err error) {
|
||||
if len(req.URL.RawPath) > 0 {
|
||||
return req.URL.RawPath, nil
|
||||
}
|
||||
return req.URL.EscapedPath(), nil
|
||||
return fastURLPathEncode(req.URL.Path), nil
|
||||
}
|
||||
|
||||
func (n ContextBasedNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
|
||||
|
|
|
@ -17,9 +17,13 @@ limitations under the License.
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
fuzz "github.com/google/gofuzz"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -114,3 +118,62 @@ func TestGenerateLink(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fastURLPathEncode_fuzz(t *testing.T) {
|
||||
specialCases := []string{"/", "//", ".", "*", "/abc%"}
|
||||
for _, test := range specialCases {
|
||||
got := fastURLPathEncode(test)
|
||||
u := url.URL{Path: test}
|
||||
expected := u.EscapedPath()
|
||||
if got != expected {
|
||||
t.Errorf("%q did not match %q", got, expected)
|
||||
}
|
||||
}
|
||||
f := fuzz.New().Funcs(
|
||||
func(s *string, c fuzz.Continue) {
|
||||
*s = randString(c.Rand)
|
||||
},
|
||||
)
|
||||
for i := 0; i < 2000; i++ {
|
||||
var test string
|
||||
f.Fuzz(&test)
|
||||
|
||||
got := fastURLPathEncode(test)
|
||||
u := url.URL{Path: test}
|
||||
expected := u.EscapedPath()
|
||||
if got != expected {
|
||||
t.Errorf("%q did not match %q", got, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unicode range fuzzer from github.com/google/gofuzz/fuzz.go
|
||||
|
||||
type charRange struct {
|
||||
first, last rune
|
||||
}
|
||||
|
||||
var unicodeRanges = []charRange{
|
||||
{0x00, 0x255},
|
||||
{' ', '~'}, // ASCII characters
|
||||
{'\u00a0', '\u02af'}, // Multi-byte encoded characters
|
||||
{'\u4e00', '\u9fff'}, // Common CJK (even longer encodings)
|
||||
}
|
||||
|
||||
// randString makes a random string up to 20 characters long. The returned string
|
||||
// may include a variety of (valid) UTF-8 encodings.
|
||||
func randString(r *rand.Rand) string {
|
||||
n := r.Intn(20)
|
||||
runes := make([]rune, n)
|
||||
for i := range runes {
|
||||
runes[i] = unicodeRanges[r.Intn(len(unicodeRanges))].choose(r)
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
// choose returns a random unicode character from the given range, using the
|
||||
// given randomness source.
|
||||
func (r *charRange) choose(rand *rand.Rand) rune {
|
||||
count := int64(r.last - r.first)
|
||||
return r.first + rune(rand.Int63n(count))
|
||||
}
|
||||
|
|
|
@ -757,6 +757,30 @@ func TestWatchHTTPTimeout(t *testing.T) {
|
|||
func BenchmarkWatchHTTP(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
|
||||
// use ASCII names to capture the cost of handling ASCII only self-links
|
||||
for i := range items {
|
||||
item := &items[i]
|
||||
item.Namespace = fmt.Sprintf("namespace-%d", i)
|
||||
item.Name = fmt.Sprintf("reasonable-name-%d", i)
|
||||
}
|
||||
|
||||
runWatchHTTPBenchmark(b, items)
|
||||
}
|
||||
|
||||
func BenchmarkWatchHTTP_UTF8(b *testing.B) {
|
||||
items := benchmarkItems(b)
|
||||
|
||||
// use UTF names to capture the cost of handling UTF-8 escaping in self-links
|
||||
for i := range items {
|
||||
item := &items[i]
|
||||
item.Namespace = fmt.Sprintf("躀痢疈蜧í柢-%d", i)
|
||||
item.Name = fmt.Sprintf("翏Ŏ熡韐-%d", i)
|
||||
}
|
||||
|
||||
runWatchHTTPBenchmark(b, items)
|
||||
}
|
||||
|
||||
func runWatchHTTPBenchmark(b *testing.B, items []example.Pod) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := handle(map[string]rest.Storage{"simples": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
|
|
Loading…
Reference in New Issue