Skip to content

Commit

Permalink
allow proxy to route based on request method
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic committed Apr 13, 2022
1 parent 4343cf3 commit 2bee87b
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 13 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/proxy-method-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: http method based routing

We added a method property to proxy routes that allows routing requests to a different backend.

https://github.com/owncloud/ocis/pull/3526
6 changes: 4 additions & 2 deletions proxy/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ type Policy struct {

// Route defines forwarding routes
type Route struct {
Type RouteType `yaml:"type"`
Endpoint string `yaml:"endpoint"`
Type RouteType `yaml:"type"`
// Method optionally limits the route to this HTTP method
Method string `yaml:"method"`
Endpoint string `yaml:"endpoint"`
// Backend is a static URL to forward the request to
Backend string `yaml:"backend"`
// Service name to look up in the registry
Expand Down
9 changes: 9 additions & 0 deletions proxy/pkg/config/defaults/defaultconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ func DefaultPolicies() []config.Policy {
Endpoint: "/remote.php/?preview=1",
Backend: "http://localhost:9115",
},
{
// send webdav REPORT requests to search service
// 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
Method: "REPORT",
Endpoint: "/dav/",
Backend: "http://localhost:9115",
},
{
Endpoint: "/remote.php/",
Service: "ocdav",
Expand Down
34 changes: 25 additions & 9 deletions proxy/pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,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
Expand All @@ -40,7 +41,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,
}
Expand Down Expand Up @@ -124,6 +125,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
Expand All @@ -137,25 +139,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
}

Expand All @@ -182,20 +195,23 @@ 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))
}
if p.Directors[policy][routeType][rt.Method] == nil {
p.Directors[policy][routeType][rt.Method] = make(map[string]func(req *http.Request))
}

reg := registry.GetRegistry()
sel := selector.NewSelector(selector.Registry(reg))

p.Directors[policy][routeType][rt.Endpoint] = func(req *http.Request) {
p.Directors[policy][routeType][rt.Method][rt.Endpoint] = func(req *http.Request) {
if rt.Service != "" {
// select next node
next, err := sel.Select(rt.Service)
Expand Down
40 changes: 38 additions & 2 deletions proxy/pkg/proxy/proxy_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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)

}
}
}

0 comments on commit 2bee87b

Please sign in to comment.