From e18d98a0a796b4769dbb4c706f8776b32504326d Mon Sep 17 00:00:00 2001 From: spacewander Date: Sat, 24 Feb 2024 11:42:22 +0800 Subject: [PATCH] filtermanager: cache URL & Cookies Signed-off-by: spacewander --- pkg/expr/cel.go | 7 ++- pkg/filtermanager/api/api.go | 17 ++++++-- pkg/filtermanager/filtermanager.go | 70 ++++++++++++++++++++++++++++-- pkg/request/request.go | 14 +----- plugins/casbin/filter.go | 3 +- plugins/hmac_auth/filter.go | 3 +- plugins/key_auth/filter.go | 3 +- plugins/oidc/filter.go | 7 ++- plugins/opa/filter.go | 2 +- plugins/tests/pkg/envoy/capi.go | 12 +++++ 10 files changed, 103 insertions(+), 35 deletions(-) diff --git a/pkg/expr/cel.go b/pkg/expr/cel.go index 7f044e6c..374ed4fa 100644 --- a/pkg/expr/cel.go +++ b/pkg/expr/cel.go @@ -31,7 +31,6 @@ import ( "mosn.io/htnn/pkg/filtermanager/api" "mosn.io/htnn/pkg/log" - pkgRequest "mosn.io/htnn/pkg/request" ) var ( @@ -205,7 +204,7 @@ func (r *request) Receive(function string, overload string, args []ref.Val) ref. case "path": return types.String(r.headers.Path()) case "url_path": - return types.String(pkgRequest.GetUrl(r.headers).Path) + return types.String(r.headers.Url().Path) case "host": return types.String(r.headers.Host()) case "scheme": @@ -216,7 +215,7 @@ func (r *request) Receive(function string, overload string, args []ref.Val) ref. name := args[0].Value().(string) return types.String(r.Header(name)) case "query_path": - return types.String(pkgRequest.GetUrl(r.headers).RawQuery) + return types.String(r.headers.Url().RawQuery) case "query": name := args[0].Value().(string) return types.String(r.Query(name)) @@ -242,7 +241,7 @@ func (r *request) Header(name string) string { } func (r *request) Query(name string) string { - query := pkgRequest.GetUrl(r.headers).Query() + query := r.headers.Url().Query() v := query[name] n := len(v) if n == 1 { diff --git a/pkg/filtermanager/api/api.go b/pkg/filtermanager/api/api.go index ddccdcd1..29ab5686 100644 --- a/pkg/filtermanager/api/api.go +++ b/pkg/filtermanager/api/api.go @@ -15,6 +15,9 @@ package api import ( + "net/http" + "net/url" + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" "google.golang.org/protobuf/reflect/protoreflect" ) @@ -94,16 +97,24 @@ func (f *PassThroughFilter) EncodeTrailers(trailers ResponseTrailerMap) ResultAc func (f *PassThroughFilter) OnLog() {} -func (f *PassThroughFilter) DecodeRequest(headers api.RequestHeaderMap, data api.BufferInstance, trailers api.RequestTrailerMap) ResultAction { +func (f *PassThroughFilter) DecodeRequest(headers RequestHeaderMap, data BufferInstance, trailers RequestTrailerMap) ResultAction { return Continue } -func (f *PassThroughFilter) EncodeResponse(headers api.ResponseHeaderMap, data api.BufferInstance, trailers api.ResponseTrailerMap) ResultAction { +func (f *PassThroughFilter) EncodeResponse(headers ResponseHeaderMap, data BufferInstance, trailers ResponseTrailerMap) ResultAction { return Continue } type HeaderMap = api.HeaderMap -type RequestHeaderMap = api.RequestHeaderMap +type RequestHeaderMap interface { + api.RequestHeaderMap + + // Url returns the parsed `url.URL` + Url() *url.URL + // Cookies returns the HTTP Cookies. + // If multiple cookies match the given name, only one cookie will be returned. + Cookies() map[string]*http.Cookie +} type ResponseHeaderMap = api.ResponseHeaderMap type DataBufferBase = api.DataBufferBase type BufferInstance = api.BufferInstance diff --git a/pkg/filtermanager/filtermanager.go b/pkg/filtermanager/filtermanager.go index fcf42ca3..c2ae4d4a 100644 --- a/pkg/filtermanager/filtermanager.go +++ b/pkg/filtermanager/filtermanager.go @@ -17,12 +17,16 @@ package filtermanager import ( "encoding/json" "errors" + "fmt" "net" + "net/http" + "net/url" "reflect" "runtime" "runtime/debug" "sort" "strconv" + "strings" "sync" xds "github.com/cncf/xds/go/xds/type/v3" @@ -33,6 +37,7 @@ import ( "mosn.io/htnn/pkg/filtermanager/api" "mosn.io/htnn/pkg/filtermanager/model" pkgPlugins "mosn.io/htnn/pkg/plugins" + "mosn.io/htnn/pkg/request" ) // We can't import package below here that will cause build failure in Mac @@ -227,6 +232,60 @@ func (m *filterManager) Reset() { m.callbacks.Reset() } +type filterManagerRequestHeaderMap struct { + capi.RequestHeaderMap + + u *url.URL + cookies map[string]*http.Cookie +} + +func (headers *filterManagerRequestHeaderMap) expire(key string) { + switch key { + case ":path": + headers.u = nil + case "cookie": + headers.cookies = nil + } +} + +func (headers *filterManagerRequestHeaderMap) Set(key, value string) { + key = strings.ToLower(key) + headers.expire(key) + headers.RequestHeaderMap.Set(key, value) +} + +func (headers *filterManagerRequestHeaderMap) Add(key, value string) { + key = strings.ToLower(key) + headers.expire(key) + headers.RequestHeaderMap.Add(key, value) +} + +func (headers *filterManagerRequestHeaderMap) Del(key string) { + key = strings.ToLower(key) + headers.expire(key) + headers.RequestHeaderMap.Del(key) +} + +func (headers *filterManagerRequestHeaderMap) Url() *url.URL { + if headers.u == nil { + path := headers.Path() + u, err := url.ParseRequestURI(path) + if err != nil { + panic(fmt.Sprintf("unexpected bad request uri given by envoy: %v", err)) + } + headers.u = u + } + return headers.u +} + +// If multiple cookies match the given name, only one cookie will be returned. +func (headers *filterManagerRequestHeaderMap) Cookies() map[string]*http.Cookie { + if headers.cookies == nil { + headers.cookies = request.ParseCookies(headers) + } + return headers.cookies +} + type filterManagerStreamInfo struct { capi.StreamInfo @@ -470,7 +529,7 @@ func (m *filterManager) localReply(v *api.LocalResponse) { m.callbacks.SendLocalReply(v.Code, msg, hdr, 0, "") } -func (m *filterManager) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) capi.StatusType { +func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream bool) capi.StatusType { if m.canSkipDecodeHeaders { return capi.Continue } @@ -479,6 +538,9 @@ func (m *filterManager) DecodeHeaders(headers api.RequestHeaderMap, endStream bo defer m.callbacks.RecoverPanic() var res api.ResultAction + headers := &filterManagerRequestHeaderMap{ + RequestHeaderMap: headers, + } m.reqHdr = headers if len(m.consumerFilters) > 0 { for _, f := range m.consumerFilters { @@ -574,7 +636,7 @@ func (m *filterManager) DecodeHeaders(headers api.RequestHeaderMap, endStream bo return capi.Running } -func (m *filterManager) DecodeData(buf api.BufferInstance, endStream bool) capi.StatusType { +func (m *filterManager) DecodeData(buf capi.BufferInstance, endStream bool) capi.StatusType { if m.canSkipDecodeData { return capi.Continue } @@ -668,7 +730,7 @@ func (m *filterManager) DecodeData(buf api.BufferInstance, endStream bool) capi. return capi.Running } -func (m *filterManager) EncodeHeaders(headers api.ResponseHeaderMap, endStream bool) capi.StatusType { +func (m *filterManager) EncodeHeaders(headers capi.ResponseHeaderMap, endStream bool) capi.StatusType { if m.canSkipEncodeHeaders { return capi.Continue } @@ -707,7 +769,7 @@ func (m *filterManager) EncodeHeaders(headers api.ResponseHeaderMap, endStream b return capi.Running } -func (m *filterManager) EncodeData(buf api.BufferInstance, endStream bool) capi.StatusType { +func (m *filterManager) EncodeData(buf capi.BufferInstance, endStream bool) capi.StatusType { if m.canSkipEncodeData { return capi.Continue } diff --git a/pkg/request/request.go b/pkg/request/request.go index 7a6636ac..13b68823 100644 --- a/pkg/request/request.go +++ b/pkg/request/request.go @@ -15,26 +15,14 @@ package request import ( - "fmt" "net/http" "net/textproto" - "net/url" "strings" "github.com/envoyproxy/envoy/contrib/golang/common/go/api" "golang.org/x/net/http/httpguts" ) -func GetUrl(headers api.RequestHeaderMap) *url.URL { - path := headers.Path() - // TODO: cache it - uri, err := url.ParseRequestURI(path) - if err != nil { - panic(fmt.Sprintf("unexpected bad request uri given by envoy: %v", err)) - } - return uri -} - // The cookie parser is from Go's http/cookie.go, which are not exported func isNotToken(r rune) bool { @@ -66,7 +54,7 @@ func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { } // If multiple cookies match the given name, only one cookie will be returned. -func GetCookies(headers api.RequestHeaderMap) map[string]*http.Cookie { +func ParseCookies(headers api.RequestHeaderMap) map[string]*http.Cookie { lines := headers.Values("Cookie") if len(lines) == 0 { return map[string]*http.Cookie{} diff --git a/plugins/casbin/filter.go b/plugins/casbin/filter.go index 5239088d..8ee0bc00 100644 --- a/plugins/casbin/filter.go +++ b/plugins/casbin/filter.go @@ -19,7 +19,6 @@ import ( "mosn.io/htnn/pkg/file" "mosn.io/htnn/pkg/filtermanager/api" - "mosn.io/htnn/pkg/request" ) func factory(c interface{}, callbacks api.FilterCallbackHandler) api.Filter { @@ -39,7 +38,7 @@ type filter struct { func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api.ResultAction { conf := f.config role, _ := headers.Get(conf.Token.Name) // role can be "" - url := request.GetUrl(headers) + url := headers.Url() policyChanged := file.IsChanged(conf.modelFile, conf.policyFile) if policyChanged && !conf.updating.Load() { diff --git a/plugins/hmac_auth/filter.go b/plugins/hmac_auth/filter.go index 969de418..848179e6 100644 --- a/plugins/hmac_auth/filter.go +++ b/plugins/hmac_auth/filter.go @@ -25,7 +25,6 @@ import ( "strings" "mosn.io/htnn/pkg/filtermanager/api" - "mosn.io/htnn/pkg/request" ) func factory(c interface{}, callbacks api.FilterCallbackHandler) api.Filter { @@ -58,7 +57,7 @@ func (f *filter) getSignContent(header api.RequestHeaderMap, accessKey string) s dh = f.config.DateHeader } date, _ := header.Get(dh) - url := request.GetUrl(header) + url := header.Url() path := url.Path if path == "" { path = "/" diff --git a/plugins/key_auth/filter.go b/plugins/key_auth/filter.go index 7b5280ed..3a8844e5 100644 --- a/plugins/key_auth/filter.go +++ b/plugins/key_auth/filter.go @@ -18,7 +18,6 @@ import ( "net/url" "mosn.io/htnn/pkg/filtermanager/api" - "mosn.io/htnn/pkg/request" ) func factory(c interface{}, callbacks api.FilterCallbackHandler) api.Filter { @@ -52,7 +51,7 @@ func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api var vals []string if key.Source == Source_QUERY { if query == nil { - query = request.GetUrl(headers).Query() + query = headers.Url().Query() } vals = query[key.Name] } else { diff --git a/plugins/oidc/filter.go b/plugins/oidc/filter.go index ea9272de..672b0b09 100644 --- a/plugins/oidc/filter.go +++ b/plugins/oidc/filter.go @@ -29,7 +29,6 @@ import ( "golang.org/x/oauth2" "mosn.io/htnn/pkg/filtermanager/api" - "mosn.io/htnn/pkg/request" ) func factory(c interface{}, callbacks api.FilterCallbackHandler) api.Filter { @@ -152,7 +151,7 @@ func (f *filter) handleCallback(headers api.RequestHeaderMap, query url.Values) } if !config.SkipNonceVerify { - nonce, ok := request.GetCookies(headers)["htnn_oidc_nonce"] + nonce, ok := headers.Cookies()["htnn_oidc_nonce"] if !ok { api.LogInfof("bad nonce, expected %s", idToken.Nonce) return &api.LocalResponse{Code: 403, Msg: "bad nonce"} @@ -208,12 +207,12 @@ func (f *filter) attachInfo(headers api.RequestHeaderMap, encodedToken string) a } func (f *filter) DecodeHeaders(headers api.RequestHeaderMap, endStream bool) api.ResultAction { - token, ok := request.GetCookies(headers)["htnn_oidc_token"] + token, ok := headers.Cookies()["htnn_oidc_token"] if ok { return f.attachInfo(headers, token.Value) } - query := request.GetUrl(headers).Query() + query := headers.Url().Query() code := query.Get("code") if code == "" { return f.handleInitRequest(headers) diff --git a/plugins/opa/filter.go b/plugins/opa/filter.go index bb8b5c31..7cb92a31 100644 --- a/plugins/opa/filter.go +++ b/plugins/opa/filter.go @@ -63,7 +63,7 @@ func mapStrsToMapStr(strs map[string][]string) map[string]string { } func (f *filter) buildInput(header api.RequestHeaderMap) map[string]interface{} { - uri := request.GetUrl(header) + uri := header.Url() headers := request.GetHeaders(header) req := map[string]interface{}{ "method": header.Method(), diff --git a/plugins/tests/pkg/envoy/capi.go b/plugins/tests/pkg/envoy/capi.go index 9e40144f..3ad6e936 100644 --- a/plugins/tests/pkg/envoy/capi.go +++ b/plugins/tests/pkg/envoy/capi.go @@ -20,12 +20,14 @@ import ( "bytes" "log" "net/http" + "net/url" "strconv" "sync" capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" "mosn.io/htnn/pkg/filtermanager/api" + "mosn.io/htnn/pkg/request" ) func init() { @@ -137,6 +139,16 @@ func (i *RequestHeaderMap) Path() string { return path } +func (i *RequestHeaderMap) Url() *url.URL { + path := i.Path() + u, _ := url.ParseRequestURI(path) + return u +} + +func (i *RequestHeaderMap) Cookies() map[string]*http.Cookie { + return request.ParseCookies(i) +} + var _ api.RequestHeaderMap = (*RequestHeaderMap)(nil) type ResponseHeaderMap struct {