allow proxy to route based on request method

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
Jörn Friedrich Dreyer
2022-04-12 14:51:16 +00:00
parent 6f59395611
commit 6bb9af5a24
4 changed files with 78 additions and 15 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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)
}
}
}