102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"proxy-pool/internal/domain/routing"
|
|
"proxy-pool/internal/gateway/dispatch"
|
|
"proxy-pool/internal/gateway/snapshot"
|
|
)
|
|
|
|
var (
|
|
ErrRouteNotFound = errors.New("no gateway routing rule matched")
|
|
ErrRouteRejected = errors.New("gateway routing rule rejected the request")
|
|
ErrDirectRouteUnsupported = errors.New("direct gateway routing is not implemented")
|
|
)
|
|
|
|
type RulesRouter struct {
|
|
rules *routing.RuleSet
|
|
}
|
|
|
|
func NewRulesRouter(rules *routing.RuleSet) *RulesRouter {
|
|
return &RulesRouter{rules: rules}
|
|
}
|
|
|
|
func (router *RulesRouter) Route(request *http.Request) (dispatch.Request, error) {
|
|
if router == nil || router.rules == nil || request == nil {
|
|
return dispatch.Request{}, ErrRouteNotFound
|
|
}
|
|
return routeWithMatcher(request, router.rules.Match)
|
|
}
|
|
|
|
// SnapshotRouter always matches against the rules stored with the current
|
|
// proxy snapshot so a request cannot combine routing from one version with
|
|
// proxy ownership from another.
|
|
type SnapshotRouter struct {
|
|
store *snapshot.Store
|
|
}
|
|
|
|
func NewSnapshotRouter(store *snapshot.Store) *SnapshotRouter {
|
|
return &SnapshotRouter{store: store}
|
|
}
|
|
|
|
func (router *SnapshotRouter) Route(request *http.Request) (dispatch.Request, error) {
|
|
if router == nil || router.store == nil || request == nil {
|
|
return dispatch.Request{}, ErrRouteNotFound
|
|
}
|
|
view := router.store.Current()
|
|
if view == nil || (!view.ValidUntil.IsZero() && !view.ValidUntil.After(time.Now().UTC())) {
|
|
return dispatch.Request{}, ErrRouteNotFound
|
|
}
|
|
return routeWithMatcher(request, view.MatchRouting)
|
|
}
|
|
|
|
type routeMatcher func(routing.Request) (routing.Rule, bool)
|
|
|
|
func routeWithMatcher(request *http.Request, match routeMatcher) (dispatch.Request, error) {
|
|
if request == nil || match == nil {
|
|
return dispatch.Request{}, ErrRouteNotFound
|
|
}
|
|
host := request.Host
|
|
if request.URL != nil && request.URL.Hostname() != "" {
|
|
host = request.URL.Hostname()
|
|
} else if parsed, _, err := net.SplitHostPort(host); err == nil {
|
|
host = parsed
|
|
}
|
|
path := "/"
|
|
if request.URL != nil && request.URL.Path != "" {
|
|
path = request.URL.Path
|
|
}
|
|
headers := make(map[string]string, len(request.Header))
|
|
for name := range request.Header {
|
|
headers[name] = request.Header.Get(name)
|
|
}
|
|
matched, ok := match(routing.Request{
|
|
Host: strings.ToLower(strings.TrimSuffix(host, ".")),
|
|
Method: request.Method,
|
|
Path: path,
|
|
Headers: headers,
|
|
})
|
|
if !ok {
|
|
return dispatch.Request{}, ErrRouteNotFound
|
|
}
|
|
switch matched.Action {
|
|
case routing.ActionProxy:
|
|
return dispatch.Request{
|
|
RoutingName: matched.Name, Upstreams: append([]string(nil), matched.Upstreams...),
|
|
Strategy: matched.Strategy, OnUnavailable: matched.OnUnavailable,
|
|
}, nil
|
|
case routing.ActionReject:
|
|
return dispatch.Request{}, fmt.Errorf("%w: %s", ErrRouteRejected, matched.Name)
|
|
case routing.ActionDirect:
|
|
return dispatch.Request{}, fmt.Errorf("%w: %s", ErrDirectRouteUnsupported, matched.Name)
|
|
default:
|
|
return dispatch.Request{}, fmt.Errorf("%w: %s has action %q", ErrRouteRejected, matched.Name, matched.Action)
|
|
}
|
|
}
|