This is an automated email from the ASF dual-hosted git repository.
Alanxtl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new d4ce45338 perf(triple): add fast paths for common content-type
canonicalization (#3416)
d4ce45338 is described below
commit d4ce453382a0e6ea18c9840e3a69ac8264790f60
Author: Harsh Mehta <[email protected]>
AuthorDate: Sun Jun 14 18:57:52 2026 +0530
perf(triple): add fast paths for common content-type canonicalization
(#3416)
* perf(triple): add fast paths for common content-type canonicalization
Signed-off-by: Harsh Mehta <[email protected]>
* perf(protocol): implement fast path for content-type canonicalization
Signed-off-by: Harsh Mehta <[email protected]>
---------
Signed-off-by: Harsh Mehta <[email protected]>
---
protocol/triple/triple_protocol/protocol.go | 110 +++++++++++++++++++----
protocol/triple/triple_protocol/protocol_test.go | 26 ++++--
2 files changed, 112 insertions(+), 24 deletions(-)
diff --git a/protocol/triple/triple_protocol/protocol.go
b/protocol/triple/triple_protocol/protocol.go
index a6f7aea0d..d8e872b99 100644
--- a/protocol/triple/triple_protocol/protocol.go
+++ b/protocol/triple/triple_protocol/protocol.go
@@ -329,27 +329,49 @@ func flushResponseWriter(w http.ResponseWriter) {
}
func canonicalizeContentType(contentType string) string {
- // Typically, clients send Content-Type in canonical form, without
- // parameters. In those cases, we'd like to avoid parsing and
- // canonicalization overhead.
- //
- // See https://www.rfc-editor.org/rfc/rfc2045.html#section-5.1 for a
full
- // grammar.
- var slashes int
- for _, r := range contentType {
- switch {
- case r >= 'a' && r <= 'z':
- case r == '.' || r == '+' || r == '-':
- case r == '/':
- slashes++
- default:
- return canonicalizeContentTypeSlow(contentType)
+ if canonical, ok := canonicalizeContentTypeFast(contentType); ok {
+ return canonical
+ }
+ return canonicalizeContentTypeSlow(contentType)
+}
+
+// canonicalizeContentTypeFast handles parameter-free types and the common
+// "; charset=<token>" case without invoking mime.ParseMediaType.
+// See https://www.rfc-editor.org/rfc/rfc2045.html#section-5.1 for a full
grammar.
+func canonicalizeContentTypeFast(contentType string) (string, bool) {
+ semi := strings.IndexByte(contentType, ';')
+ if semi < 0 {
+ if isLowercaseMediaType(contentType) {
+ return contentType, true
}
+ return "", false
}
- if slashes == 1 {
- return contentType
+
+ base := contentType[:semi]
+ param := contentType[semi+1:]
+
+ if !isLowercaseMediaType(base) {
+ return "", false
}
- return canonicalizeContentTypeSlow(contentType)
+ if strings.Contains(param, ";") {
+ return "", false
+ }
+
+ const prefix = " charset="
+ if !strings.HasPrefix(param, prefix) {
+ return "", false
+ }
+
+ charset := param[len(prefix):]
+ if !isSimpleCharsetToken(charset) {
+ return "", false
+ }
+
+ lower := strings.ToLower(charset)
+ if lower == charset {
+ return contentType, true
+ }
+ return base + "; charset=" + lower, true
}
func canonicalizeContentTypeSlow(contentType string) string {
@@ -367,3 +389,55 @@ func canonicalizeContentTypeSlow(contentType string)
string {
}
return mime.FormatMediaType(base, params)
}
+
+// isLowercaseMediaType reports whether s is a well-formed, already-lowercase
+// media type: both type and subtype non-empty, separated by exactly one '/'.
+func isLowercaseMediaType(s string) bool {
+ slash := strings.IndexByte(s, '/')
+ if slash <= 0 || slash == len(s)-1 {
+ return false
+ }
+ if strings.IndexByte(s[slash+1:], '/') >= 0 {
+ return false
+ }
+ return isLowercaseToken(s[:slash]) && isLowercaseToken(s[slash+1:])
+}
+
+// isLowercaseToken reports whether s is a non-empty token of lowercase
letters,
+// digits, '.', '+', and '-'.
+func isLowercaseToken(s string) bool {
+ if s == "" {
+ return false
+ }
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ switch {
+ case c >= 'a' && c <= 'z':
+ case c >= '0' && c <= '9':
+ case c == '.' || c == '+' || c == '-':
+ default:
+ return false
+ }
+ }
+ return true
+}
+
+// isSimpleCharsetToken reports whether s is a non-empty token composed of
+// letters, digits, '-', '_', and '.'.
+func isSimpleCharsetToken(s string) bool {
+ if s == "" {
+ return false
+ }
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ switch {
+ case c >= 'a' && c <= 'z':
+ case c >= 'A' && c <= 'Z':
+ case c >= '0' && c <= '9':
+ case c == '-' || c == '_' || c == '.':
+ default:
+ return false
+ }
+ }
+ return true
+}
diff --git a/protocol/triple/triple_protocol/protocol_test.go
b/protocol/triple/triple_protocol/protocol_test.go
index ffbc09ad2..9ffd3a428 100644
--- a/protocol/triple/triple_protocol/protocol_test.go
+++ b/protocol/triple/triple_protocol/protocol_test.go
@@ -29,10 +29,17 @@ func TestCanonicalizeContentType(t *testing.T) {
arg string
want string
}{
+ // slow path: base type needs case normalization
{name: "uppercase should be normalized", arg:
"APPLICATION/json", want: "application/json"},
- {name: "charset param should be treated as lowercase", arg:
"application/json; charset=UTF-8", want: "application/json; charset=utf-8"},
- {name: "non charset param should not be changed", arg:
"multipart/form-data; boundary=fooBar", want: "multipart/form-data;
boundary=fooBar"},
{name: "no parameters should be normalized", arg:
"APPLICATION/json; ", want: "application/json"},
+ // slow path: non-charset parameter
+ {name: "non charset param should not be changed", arg:
"multipart/form-data; boundary=fooBar", want: "multipart/form-data;
boundary=fooBar"},
+ // fast path: charset parameter normalization
+ {name: "charset param uppercase normalized via fast path", arg:
"application/json; charset=UTF-8", want: "application/json; charset=utf-8"},
+ {name: "charset param already lowercase returned as-is", arg:
"application/json; charset=utf-8", want: "application/json; charset=utf-8"},
+ // malformed: must fall through to slow path unchanged (not
rewritten by fast path)
+ {name: "malformed missing type", arg: "/json; charset=UTF-8",
want: "/json; charset=UTF-8"},
+ {name: "malformed missing subtype", arg: "application/;
charset=UTF-8", want: "application/; charset=UTF-8"},
}
for _, tt := range tests {
tt := tt
@@ -45,23 +52,30 @@ func TestCanonicalizeContentType(t *testing.T) {
func BenchmarkCanonicalizeContentType(b *testing.B) {
b.Run("simple", func(b *testing.B) {
+ b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = canonicalizeContentType("application/json")
}
- b.ReportAllocs()
})
- b.Run("with charset", func(b *testing.B) {
+ b.Run("charset canonical", func(b *testing.B) {
+ b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = canonicalizeContentType("application/json;
charset=utf-8")
}
+ })
+
+ b.Run("charset uppercase", func(b *testing.B) {
b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ _ = canonicalizeContentType("application/json;
charset=UTF-8")
+ }
})
- b.Run("with other param", func(b *testing.B) {
+ b.Run("non-charset param", func(b *testing.B) {
+ b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = canonicalizeContentType("application/json;
foo=utf-8")
}
- b.ReportAllocs()
})
}