From 6bb9af5a24399aa3af5e317c8a1a4615f8ebd494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 12 Apr 2022 14:51:16 +0000 Subject: [PATCH] allow proxy to route based on request method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- proxy/pkg/config/config.go | 10 +++--- proxy/pkg/config/defaults/defaultconfig.go | 9 +++++ proxy/pkg/proxy/proxy.go | 34 +++++++++++++----- proxy/pkg/proxy/proxy_test.go | 40 ++++++++++++++++++++-- 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/proxy/pkg/config/config.go b/proxy/pkg/config/config.go index 6cda66b8f3..d5c316b1c5 100644 --- a/proxy/pkg/config/config.go +++ b/proxy/pkg/config/config.go @@ -45,10 +45,12 @@ type Policy struct { // Route define forwarding routes type Route struct { - Type RouteType `ocisConfig:"type"` - Endpoint string `ocisConfig:"endpoint"` - Backend string `ocisConfig:"backend"` - ApacheVHost bool `ocisConfig:"apache-vhost"` + Type RouteType `ocisConfig:"type"` + // Method optionally limits the route to this HTTP method + Method string `ocisConfig:"method"` + Endpoint string `ocisConfig:"endpoint"` + Backend string `ocisConfig:"backend"` + ApacheVHost bool `ocisConfig:"apache-vhost"` } // RouteType defines the type of a route diff --git a/proxy/pkg/config/defaults/defaultconfig.go b/proxy/pkg/config/defaults/defaultconfig.go index eaa328b5a0..776941930b 100644 --- a/proxy/pkg/config/defaults/defaultconfig.go +++ b/proxy/pkg/config/defaults/defaultconfig.go @@ -96,6 +96,15 @@ func DefaultPolicies() []config.Policy { Endpoint: "/remote.php/?preview=1", Backend: "http://localhost:9115", }, + { + // TODO the actual REPORT goes to /dav/files/{username}, which is user specific ... how would this work in a spaces world? + // TODO what paths are returned? the href contains the full path so it should be possible to return urls from other spaces? + // TODO or we allow a REPORT on /dav/spaces to search all spaces and /dav/space/{spaceid} to search a specific space + // send webdav REPORT requests to search service + Method: "REPORT", + Endpoint: "/dav/", + Backend: "http://localhost:????", // TODO use registry? + }, { Endpoint: "/remote.php/", Backend: "http://localhost:9140", diff --git a/proxy/pkg/proxy/proxy.go b/proxy/pkg/proxy/proxy.go index 7681defb80..89ff7b3a7a 100644 --- a/proxy/pkg/proxy/proxy.go +++ b/proxy/pkg/proxy/proxy.go @@ -27,7 +27,8 @@ import ( // MultiHostReverseProxy extends "httputil" to support multiple hosts with different policies type MultiHostReverseProxy struct { httputil.ReverseProxy - Directors map[string]map[config.RouteType]map[string]func(req *http.Request) + // Directors holds policy route type method endpoint Director + Directors map[string]map[config.RouteType]map[string]map[string]func(req *http.Request) PolicySelector policy.Selector logger log.Logger config *config.Config @@ -38,7 +39,7 @@ func NewMultiHostReverseProxy(opts ...Option) *MultiHostReverseProxy { options := newOptions(opts...) rp := &MultiHostReverseProxy{ - Directors: make(map[string]map[config.RouteType]map[string]func(req *http.Request)), + Directors: make(map[string]map[config.RouteType]map[string]map[string]func(req *http.Request)), logger: options.Logger, config: options.Config, } @@ -117,6 +118,7 @@ func (p *MultiHostReverseProxy) directorSelectionDirector(r *http.Request) { return } + method := "" // find matching director for _, rt := range config.RouteTypes { var handler func(string, url.URL) bool @@ -130,25 +132,36 @@ func (p *MultiHostReverseProxy) directorSelectionDirector(r *http.Request) { default: handler = p.prefixRouteMatcher } - for endpoint := range p.Directors[pol][rt] { + if p.Directors[pol][rt][r.Method] != nil { + // use specific method + method = r.Method + } + for endpoint := range p.Directors[pol][rt][method] { if handler(endpoint, *r.URL) { p.logger.Debug(). Str("policy", pol). + Str("method", r.Method). Str("prefix", endpoint). Str("path", r.URL.Path). Str("routeType", string(rt)). Msg("director found") - p.Directors[pol][rt][endpoint](r) + p.Directors[pol][rt][method][endpoint](r) return } } } // override default director with root. If any - if p.Directors[pol][config.PrefixRoute]["/"] != nil { - p.Directors[pol][config.PrefixRoute]["/"](r) + switch { + case p.Directors[pol][config.PrefixRoute][method]["/"] != nil: + // try specific method + p.Directors[pol][config.PrefixRoute][method]["/"](r) + return + case p.Directors[pol][config.PrefixRoute][""]["/"] != nil: + // fallback to unspecific method + p.Directors[pol][config.PrefixRoute][""]["/"](r) return } @@ -175,16 +188,19 @@ func singleJoiningSlash(a, b string) string { func (p *MultiHostReverseProxy) AddHost(policy string, target *url.URL, rt config.Route) { targetQuery := target.RawQuery if p.Directors[policy] == nil { - p.Directors[policy] = make(map[config.RouteType]map[string]func(req *http.Request)) + p.Directors[policy] = make(map[config.RouteType]map[string]map[string]func(req *http.Request)) } routeType := config.DefaultRouteType if rt.Type != "" { routeType = rt.Type } if p.Directors[policy][routeType] == nil { - p.Directors[policy][routeType] = make(map[string]func(req *http.Request)) + p.Directors[policy][routeType] = make(map[string]map[string]func(req *http.Request)) } - p.Directors[policy][routeType][rt.Endpoint] = func(req *http.Request) { + if p.Directors[policy][routeType][rt.Method] == nil { + p.Directors[policy][routeType][rt.Method] = make(map[string]func(req *http.Request)) + } + p.Directors[policy][routeType][rt.Method][rt.Endpoint] = func(req *http.Request) { req.URL.Scheme = target.Scheme req.URL.Host = target.Host // Apache deployments host addresses need to match on req.Host and req.URL.Host diff --git a/proxy/pkg/proxy/proxy_test.go b/proxy/pkg/proxy/proxy_test.go index f64362c491..213882fb9f 100644 --- a/proxy/pkg/proxy/proxy_test.go +++ b/proxy/pkg/proxy/proxy_test.go @@ -1,15 +1,19 @@ package proxy import ( + "fmt" + "net/http" + "net/http/httptest" "net/url" "testing" + "github.com/owncloud/ocis/proxy/pkg/config" "github.com/owncloud/ocis/proxy/pkg/config/defaults" ) type matchertest struct { - endpoint, target string - matches bool + method, endpoint, target string + matches bool } func TestPrefixRouteMatcher(t *testing.T) { @@ -99,3 +103,35 @@ func TestSingleJoiningSlash(t *testing.T) { } } } + +func TestDirectorSelectionDirector(t *testing.T) { + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "ok") + })) + defer svr.Close() + + p := NewMultiHostReverseProxy(Config(&config.Config{ + PolicySelector: &config.PolicySelector{ + Static: &config.StaticSelectorConf{ + Policy: "default", + }, + }, + })) + p.AddHost("default", &url.URL{Host: "ocdav"}, config.Route{Type: config.PrefixRoute, Method: "", Endpoint: "/dav", Backend: "ocdav"}) + p.AddHost("default", &url.URL{Host: "ocis-webdav"}, config.Route{Type: config.PrefixRoute, Method: "REPORT", Endpoint: "/dav", Backend: "ocis-webdav"}) + + table := []matchertest{ + {method: "PROPFIND", endpoint: "/dav/files/demo/", target: "ocdav"}, + {method: "REPORT", endpoint: "/dav/files/demo/", target: "ocis-webdav"}, + } + + for _, test := range table { + r := httptest.NewRequest(http.MethodGet, "/dav/files/demo/", nil) + p.directorSelectionDirector(r) + if r.Host != test.target { + t.Errorf("TestDirectorSelectionDirector got host %s expected %s", r.Host, test.target) + + } + } +}