type http/tcpmux proxy support route_by_http_user, tcpmux support passthourgh mode (#2932)

This commit is contained in:
fatedier
2022-05-26 23:57:30 +08:00
committed by GitHub
parent bd89eaba2f
commit 4af85da0c2
22 changed files with 606 additions and 283 deletions

View File

@@ -24,18 +24,24 @@ import (
"github.com/fatedier/frp/pkg/util/util"
"github.com/fatedier/frp/pkg/util/vhost"
gnet "github.com/fatedier/golib/net"
)
type HTTPConnectTCPMuxer struct {
*vhost.Muxer
passthrough bool
authRequired bool // Not supported until we really need this.
}
func NewHTTPConnectTCPMuxer(listener net.Listener, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
mux, err := vhost.NewMuxer(listener, getHostFromHTTPConnect, nil, sendHTTPOk, nil, timeout)
return &HTTPConnectTCPMuxer{mux}, err
func NewHTTPConnectTCPMuxer(listener net.Listener, passthrough bool, timeout time.Duration) (*HTTPConnectTCPMuxer, error) {
ret := &HTTPConnectTCPMuxer{passthrough: passthrough, authRequired: false}
mux, err := vhost.NewMuxer(listener, ret.getHostFromHTTPConnect, nil, ret.sendConnectResponse, nil, timeout)
ret.Muxer = mux
return ret, err
}
func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
func (muxer *HTTPConnectTCPMuxer) readHTTPConnectRequest(rd io.Reader) (host string, httpUser string, err error) {
bufioReader := bufio.NewReader(rd)
req, err := http.ReadRequest(bufioReader)
@@ -49,20 +55,40 @@ func readHTTPConnectRequest(rd io.Reader) (host string, err error) {
}
host, _ = util.CanonicalHost(req.Host)
proxyAuth := req.Header.Get("Proxy-Authorization")
if proxyAuth != "" {
httpUser, _, _ = util.ParseBasicAuth(proxyAuth)
}
return
}
func sendHTTPOk(c net.Conn) error {
func (muxer *HTTPConnectTCPMuxer) sendConnectResponse(c net.Conn, reqInfo map[string]string) error {
if muxer.passthrough {
return nil
}
return util.OkResponse().Write(c)
}
func getHostFromHTTPConnect(c net.Conn) (_ net.Conn, _ map[string]string, err error) {
func (muxer *HTTPConnectTCPMuxer) getHostFromHTTPConnect(c net.Conn) (net.Conn, map[string]string, error) {
reqInfoMap := make(map[string]string, 0)
host, err := readHTTPConnectRequest(c)
sc, rd := gnet.NewSharedConn(c)
host, httpUser, err := muxer.readHTTPConnectRequest(rd)
if err != nil {
return nil, reqInfoMap, err
}
reqInfoMap["Host"] = host
reqInfoMap["Scheme"] = "tcp"
return c, reqInfoMap, nil
reqInfoMap["HTTPUser"] = httpUser
var outConn net.Conn = c
if muxer.passthrough {
outConn = sc
if muxer.authRequired && httpUser == "" {
util.ProxyUnauthorizedResponse().Write(c)
outConn = c
}
}
return outConn, reqInfoMap, nil
}

View File

@@ -15,6 +15,7 @@
package util
import (
"encoding/base64"
"net"
"net/http"
"strings"
@@ -34,6 +35,20 @@ func OkResponse() *http.Response {
return res
}
func ProxyUnauthorizedResponse() *http.Response {
header := make(http.Header)
header.Set("Proxy-Authenticate", `Basic realm="Restricted"`)
res := &http.Response{
Status: "Proxy Authentication Required",
StatusCode: 407,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: header,
}
return res
}
// canonicalHost strips port from host if present and returns the canonicalized
// host name.
func CanonicalHost(host string) (string, error) {
@@ -64,3 +79,21 @@ func hasPort(host string) bool {
}
return host[0] == '[' && strings.Contains(host, "]:")
}
func ParseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic "
// Case insensitive prefix match. See Issue 22736.
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
return
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}

View File

@@ -23,17 +23,19 @@ import (
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
frpLog "github.com/fatedier/frp/pkg/util/log"
"github.com/fatedier/frp/pkg/util/util"
frpIo "github.com/fatedier/golib/io"
"github.com/fatedier/golib/pool"
)
var (
ErrNoDomain = errors.New("no such domain")
ErrNoRouteFound = errors.New("no route found")
)
type HTTPReverseProxyOptions struct {
@@ -56,17 +58,22 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
vhostRouter: vhostRouter,
}
proxy := &ReverseProxy{
// Modify incoming requests by route policies.
Director: func(req *http.Request) {
req.URL.Scheme = "http"
url := req.Context().Value(RouteInfoURL).(string)
routeByHTTPUser := req.Context().Value(RouteInfoHTTPUser).(string)
oldHost, _ := util.CanonicalHost(req.Context().Value(RouteInfoHost).(string))
rc := rp.GetRouteConfig(oldHost, url)
rc := rp.GetRouteConfig(oldHost, url, routeByHTTPUser)
if rc != nil {
if rc.RewriteHost != "" {
req.Host = rc.RewriteHost
}
// Set {domain}.{location} as URL host here to let http transport reuse connections.
req.URL.Host = rc.Domain + "." + base64.StdEncoding.EncodeToString([]byte(rc.Location))
// Set {domain}.{location}.{routeByHTTPUser} as URL host here to let http transport reuse connections.
// TODO(fatedier): use proxy name instead?
req.URL.Host = rc.Domain + "." +
base64.StdEncoding.EncodeToString([]byte(rc.Location)) + "." +
base64.StdEncoding.EncodeToString([]byte(rc.RouteByHTTPUser))
for k, v := range rc.Headers {
req.Header.Set(k, v)
@@ -76,14 +83,30 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
}
},
// Create a connection to one proxy routed by route policy.
Transport: &http.Transport{
ResponseHeaderTimeout: rp.responseHeaderTimeout,
IdleConnTimeout: 60 * time.Second,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
url := ctx.Value(RouteInfoURL).(string)
host, _ := util.CanonicalHost(ctx.Value(RouteInfoHost).(string))
routerByHTTPUser := ctx.Value(RouteInfoHTTPUser).(string)
remote := ctx.Value(RouteInfoRemote).(string)
return rp.CreateConnection(host, url, remote)
return rp.CreateConnection(host, url, routerByHTTPUser, remote)
},
Proxy: func(req *http.Request) (*url.URL, error) {
// Use proxy mode if there is host in HTTP first request line.
// GET http://example.com/ HTTP/1.1
// Host: example.com
//
// Normal:
// GET / HTTP/1.1
// Host: example.com
urlHost := req.Context().Value(RouteInfoURLHost).(string)
if urlHost != "" {
return req.URL, nil
}
return nil, nil
},
},
BufferPool: newWrapPool(),
@@ -101,7 +124,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
// Register register the route config to reverse proxy
// reverse proxy will use CreateConnFn from routeCfg to create a connection to the remote service
func (rp *HTTPReverseProxy) Register(routeCfg RouteConfig) error {
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
err := rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, routeCfg.RouteByHTTPUser, &routeCfg)
if err != nil {
return err
}
@@ -109,28 +132,29 @@ func (rp *HTTPReverseProxy) Register(routeCfg RouteConfig) error {
}
// UnRegister unregister route config by domain and location
func (rp *HTTPReverseProxy) UnRegister(domain string, location string) {
rp.vhostRouter.Del(domain, location)
func (rp *HTTPReverseProxy) UnRegister(routeCfg RouteConfig) {
rp.vhostRouter.Del(routeCfg.Domain, routeCfg.Location, routeCfg.RouteByHTTPUser)
}
func (rp *HTTPReverseProxy) GetRouteConfig(domain string, location string) *RouteConfig {
vr, ok := rp.getVhost(domain, location)
func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser string) *RouteConfig {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok {
frpLog.Debug("get new HTTP request host [%s] path [%s] httpuser [%s]", domain, location, routeByHTTPUser)
return vr.payload.(*RouteConfig)
}
return nil
}
func (rp *HTTPReverseProxy) GetRealHost(domain string, location string) (host string) {
vr, ok := rp.getVhost(domain, location)
func (rp *HTTPReverseProxy) GetRealHost(domain, location, routeByHTTPUser string) (host string) {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok {
host = vr.payload.(*RouteConfig).RewriteHost
}
return
}
func (rp *HTTPReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location)
func (rp *HTTPReverseProxy) GetHeaders(domain, location, routeByHTTPUser string) (headers map[string]string) {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok {
headers = vr.payload.(*RouteConfig).Headers
}
@@ -138,19 +162,19 @@ func (rp *HTTPReverseProxy) GetHeaders(domain string, location string) (headers
}
// CreateConnection create a new connection by route config
func (rp *HTTPReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location)
func (rp *HTTPReverseProxy) CreateConnection(domain, location, routeByHTTPUser string, remoteAddr string) (net.Conn, error) {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok {
fn := vr.payload.(*RouteConfig).CreateConnFn
if fn != nil {
return fn(remoteAddr)
}
}
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
return nil, fmt.Errorf("%v: %s %s %s", ErrNoRouteFound, domain, location, routeByHTTPUser)
}
func (rp *HTTPReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
vr, ok := rp.getVhost(domain, location)
func (rp *HTTPReverseProxy) CheckAuth(domain, location, routeByHTTPUser, user, passwd string) bool {
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
if ok {
checkUser := vr.payload.(*RouteConfig).Username
checkPasswd := vr.payload.(*RouteConfig).Password
@@ -161,45 +185,120 @@ func (rp *HTTPReverseProxy) CheckAuth(domain, location, user, passwd string) boo
return true
}
// getVhost get vhost router by domain and location
func (rp *HTTPReverseProxy) getVhost(domain string, location string) (vr *Router, ok bool) {
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, ok = rp.vhostRouter.Get(domain, location)
if ok {
return
}
domainSplit := strings.Split(domain, ".")
if len(domainSplit) < 3 {
// getVhost trys to get vhost router by route policy.
func (rp *HTTPReverseProxy) getVhost(domain, location, routeByHTTPUser string) (*Router, bool) {
findRouter := func(inDomain, inLocation, inRouteByHTTPUser string) (*Router, bool) {
vr, ok := rp.vhostRouter.Get(inDomain, inLocation, inRouteByHTTPUser)
if ok {
return vr, ok
}
// Try to check if there is one proxy that doesn't specify routerByHTTPUser, it means match all.
vr, ok = rp.vhostRouter.Get(inDomain, inLocation, "")
if ok {
return vr, ok
}
return nil, false
}
// First we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, ok := findRouter(domain, location, routeByHTTPUser)
if ok {
return vr, ok
}
// e.g. domain = test.example.com, try to match wildcard domains.
// *.example.com
// *.com
domainSplit := strings.Split(domain, ".")
for {
if len(domainSplit) < 3 {
return nil, false
break
}
domainSplit[0] = "*"
domain = strings.Join(domainSplit, ".")
vr, ok = rp.vhostRouter.Get(domain, location)
vr, ok = findRouter(domain, location, routeByHTTPUser)
if ok {
return vr, true
}
domainSplit = domainSplit[1:]
}
// Finally, try to check if there is one proxy that domain is "*" means match all domains.
vr, ok = findRouter("*", location, routeByHTTPUser)
if ok {
return vr, true
}
return nil, false
}
func (rp *HTTPReverseProxy) connectHandler(rw http.ResponseWriter, req *http.Request) {
hj, ok := rw.(http.Hijacker)
if !ok {
rw.WriteHeader(http.StatusInternalServerError)
return
}
client, _, err := hj.Hijack()
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
return
}
url := req.Context().Value(RouteInfoURL).(string)
routeByHTTPUser := req.Context().Value(RouteInfoHTTPUser).(string)
domain, _ := util.CanonicalHost(req.Context().Value(RouteInfoHost).(string))
remoteAddr := req.Context().Value(RouteInfoRemote).(string)
remote, err := rp.CreateConnection(domain, url, routeByHTTPUser, remoteAddr)
if err != nil {
http.Error(rw, "Failed", http.StatusBadRequest)
client.Close()
return
}
req.Write(remote)
go frpIo.Join(remote, client)
}
func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Request {
newctx := req.Context()
newctx = context.WithValue(newctx, RouteInfoURL, req.URL.Path)
newctx = context.WithValue(newctx, RouteInfoHost, req.Host)
newctx = context.WithValue(newctx, RouteInfoURLHost, req.URL.Host)
user := ""
// If url host isn't empty, it's a proxy request. Get http user from Proxy-Authorization header.
if req.URL.Host != "" {
proxyAuth := req.Header.Get("Proxy-Authorization")
if proxyAuth != "" {
user, _, _ = parseBasicAuth(proxyAuth)
}
}
if user == "" {
user, _, _ = req.BasicAuth()
}
newctx = context.WithValue(newctx, RouteInfoHTTPUser, user)
newctx = context.WithValue(newctx, RouteInfoRemote, req.RemoteAddr)
return req.Clone(newctx)
}
func (rp *HTTPReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
domain, _ := util.CanonicalHost(req.Host)
location := req.URL.Path
user, passwd, _ := req.BasicAuth()
if !rp.CheckAuth(domain, location, user, passwd) {
if !rp.CheckAuth(domain, location, user, user, passwd) {
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
rp.proxy.ServeHTTP(rw, req)
newreq := rp.injectRequestInfoToCtx(req)
if req.Method == http.MethodConnect {
rp.connectHandler(rw, newreq)
} else {
rp.proxy.ServeHTTP(rw, newreq)
}
}
type wrapPool struct{}

View File

@@ -8,6 +8,7 @@ package vhost
import (
"context"
"encoding/base64"
"fmt"
"io"
"log"
@@ -209,6 +210,24 @@ func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response
return true
}
func parseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic "
// Case insensitive prefix match. See Issue 22736.
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
return
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
@@ -238,13 +257,6 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
}
// =============================
// Modified for frp
outreq = outreq.Clone(context.WithValue(outreq.Context(), RouteInfoURL, req.URL.Path))
outreq = outreq.Clone(context.WithValue(outreq.Context(), RouteInfoHost, req.Host))
outreq = outreq.Clone(context.WithValue(outreq.Context(), RouteInfoRemote, req.RemoteAddr))
// =============================
p.Director(outreq)
outreq.Close = false

View File

@@ -11,33 +11,42 @@ var (
ErrRouterConfigConflict = errors.New("router config conflict")
)
type routerByHTTPUser map[string][]*Router
type Routers struct {
RouterByDomain map[string][]*Router
mutex sync.RWMutex
indexByDomain map[string]routerByHTTPUser
mutex sync.RWMutex
}
type Router struct {
domain string
location string
httpUser string
// store any object here
payload interface{}
}
func NewRouters() *Routers {
return &Routers{
RouterByDomain: make(map[string][]*Router),
indexByDomain: make(map[string]routerByHTTPUser),
}
}
func (r *Routers) Add(domain, location string, payload interface{}) error {
func (r *Routers) Add(domain, location, httpUser string, payload interface{}) error {
r.mutex.Lock()
defer r.mutex.Unlock()
if _, exist := r.exist(domain, location); exist {
if _, exist := r.exist(domain, location, httpUser); exist {
return ErrRouterConfigConflict
}
vrs, found := r.RouterByDomain[domain]
routersByHTTPUser, found := r.indexByDomain[domain]
if !found {
routersByHTTPUser = make(map[string][]*Router)
}
vrs, found := routersByHTTPUser[httpUser]
if !found {
vrs = make([]*Router, 0, 1)
}
@@ -45,20 +54,27 @@ func (r *Routers) Add(domain, location string, payload interface{}) error {
vr := &Router{
domain: domain,
location: location,
httpUser: httpUser,
payload: payload,
}
vrs = append(vrs, vr)
sort.Sort(sort.Reverse(ByLocation(vrs)))
r.RouterByDomain[domain] = vrs
routersByHTTPUser[httpUser] = vrs
r.indexByDomain[domain] = routersByHTTPUser
return nil
}
func (r *Routers) Del(domain, location string) {
func (r *Routers) Del(domain, location, httpUser string) {
r.mutex.Lock()
defer r.mutex.Unlock()
vrs, found := r.RouterByDomain[domain]
routersByHTTPUser, found := r.indexByDomain[domain]
if !found {
return
}
vrs, found := routersByHTTPUser[httpUser]
if !found {
return
}
@@ -68,40 +84,46 @@ func (r *Routers) Del(domain, location string) {
newVrs = append(newVrs, vr)
}
}
r.RouterByDomain[domain] = newVrs
routersByHTTPUser[httpUser] = newVrs
}
func (r *Routers) Get(host, path string) (vr *Router, exist bool) {
func (r *Routers) Get(host, path, httpUser string) (vr *Router, exist bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
vrs, found := r.RouterByDomain[host]
routersByHTTPUser, found := r.indexByDomain[host]
if !found {
return
}
vrs, found := routersByHTTPUser[httpUser]
if !found {
return
}
// can't support load balance, will to do
for _, vr = range vrs {
if strings.HasPrefix(path, vr.location) {
return vr, true
}
}
return
}
func (r *Routers) exist(host, path string) (vr *Router, exist bool) {
vrs, found := r.RouterByDomain[host]
func (r *Routers) exist(host, path, httpUser string) (route *Router, exist bool) {
routersByHTTPUser, found := r.indexByDomain[host]
if !found {
return
}
routers, found := routersByHTTPUser[httpUser]
if !found {
return
}
for _, vr = range vrs {
if path == vr.location {
return vr, true
for _, route = range routers {
if path == route.location {
return route, true
}
}
return
}

View File

@@ -29,16 +29,19 @@ import (
type RouteInfo string
const (
RouteInfoURL RouteInfo = "url"
RouteInfoHost RouteInfo = "host"
RouteInfoRemote RouteInfo = "remote"
RouteInfoURL RouteInfo = "url"
RouteInfoHost RouteInfo = "host"
RouteInfoHTTPUser RouteInfo = "httpUser"
RouteInfoRemote RouteInfo = "remote"
RouteInfoURLHost RouteInfo = "urlHost"
)
type muxFunc func(net.Conn) (net.Conn, map[string]string, error)
type httpAuthFunc func(net.Conn, string, string, string) (bool, error)
type hostRewriteFunc func(net.Conn, string) (net.Conn, error)
type successFunc func(net.Conn) error
type successFunc func(net.Conn, map[string]string) error
// Muxer is only used for https and tcpmux proxy.
type Muxer struct {
listener net.Listener
timeout time.Duration
@@ -49,7 +52,15 @@ type Muxer struct {
registryRouter *Routers
}
func NewMuxer(listener net.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, successFunc successFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *Muxer, err error) {
func NewMuxer(
listener net.Listener,
vhostFunc muxFunc,
authFunc httpAuthFunc,
successFunc successFunc,
rewriteFunc hostRewriteFunc,
timeout time.Duration,
) (mux *Muxer, err error) {
mux = &Muxer{
listener: listener,
timeout: timeout,
@@ -67,12 +78,13 @@ type CreateConnFunc func(remoteAddr string) (net.Conn, error)
// RouteConfig is the params used to match HTTP requests
type RouteConfig struct {
Domain string
Location string
RewriteHost string
Username string
Password string
Headers map[string]string
Domain string
Location string
RewriteHost string
Username string
Password string
Headers map[string]string
RouteByHTTPUser string
CreateConnFn CreateConnFunc
}
@@ -81,49 +93,66 @@ type RouteConfig struct {
// then rewrite the host header to rewriteHost
func (v *Muxer) Listen(ctx context.Context, cfg *RouteConfig) (l *Listener, err error) {
l = &Listener{
name: cfg.Domain,
location: cfg.Location,
rewriteHost: cfg.RewriteHost,
userName: cfg.Username,
passWord: cfg.Password,
mux: v,
accept: make(chan net.Conn),
ctx: ctx,
name: cfg.Domain,
location: cfg.Location,
routeByHTTPUser: cfg.RouteByHTTPUser,
rewriteHost: cfg.RewriteHost,
userName: cfg.Username,
passWord: cfg.Password,
mux: v,
accept: make(chan net.Conn),
ctx: ctx,
}
err = v.registryRouter.Add(cfg.Domain, cfg.Location, l)
err = v.registryRouter.Add(cfg.Domain, cfg.Location, cfg.RouteByHTTPUser, l)
if err != nil {
return
}
return l, nil
}
func (v *Muxer) getListener(name, path string) (l *Listener, exist bool) {
func (v *Muxer) getListener(name, path, httpUser string) (*Listener, bool) {
findRouter := func(inName, inPath, inHTTPUser string) (*Listener, bool) {
vr, ok := v.registryRouter.Get(inName, inPath, httpUser)
if ok {
return vr.payload.(*Listener), true
}
// Try to check if there is one proxy that doesn't specify routerByHTTPUser, it means match all.
vr, ok = v.registryRouter.Get(inName, inPath, "")
if ok {
return vr.payload.(*Listener), true
}
return nil, false
}
// first we check the full hostname
// if not exist, then check the wildcard_domain such as *.example.com
vr, found := v.registryRouter.Get(name, path)
if found {
return vr.payload.(*Listener), true
l, ok := findRouter(name, path, httpUser)
if ok {
return l, true
}
domainSplit := strings.Split(name, ".")
if len(domainSplit) < 3 {
return
}
for {
if len(domainSplit) < 3 {
return
break
}
domainSplit[0] = "*"
name = strings.Join(domainSplit, ".")
vr, found = v.registryRouter.Get(name, path)
if found {
return vr.payload.(*Listener), true
l, ok = findRouter(name, path, httpUser)
if ok {
return l, true
}
domainSplit = domainSplit[1:]
}
// Finally, try to check if there is one proxy that domain is "*" means match all domains.
l, ok = findRouter("*", path, httpUser)
if ok {
return l, true
}
return nil, false
}
func (v *Muxer) run() {
@@ -151,25 +180,26 @@ func (v *Muxer) handle(c net.Conn) {
name := strings.ToLower(reqInfoMap["Host"])
path := strings.ToLower(reqInfoMap["Path"])
l, ok := v.getListener(name, path)
httpUser := reqInfoMap["HTTPUser"]
l, ok := v.getListener(name, path, httpUser)
if !ok {
res := notFoundResponse()
res.Write(c)
log.Debug("http request for host [%s] path [%s] not found", name, path)
log.Debug("http request for host [%s] path [%s] httpUser [%s] not found", name, path, httpUser)
c.Close()
return
}
xl := xlog.FromContextSafe(l.ctx)
if v.successFunc != nil {
if err := v.successFunc(c); err != nil {
if err := v.successFunc(c, reqInfoMap); err != nil {
xl.Info("success func failure on vhost connection: %v", err)
c.Close()
return
}
}
// if authFunc is exist and userName/password is set
// if authFunc is exist and username/password is set
// then verify user access
if l.mux.authFunc != nil && l.userName != "" && l.passWord != "" {
bAccess, err := l.mux.authFunc(c, l.userName, l.passWord, reqInfoMap["Authorization"])
@@ -188,7 +218,7 @@ func (v *Muxer) handle(c net.Conn) {
}
c = sConn
xl.Debug("get new http request host [%s] path [%s]", name, path)
xl.Debug("new request host [%s] path [%s] httpUser [%s]", name, path, httpUser)
err = errors.PanicToError(func() {
l.accept <- c
})
@@ -198,14 +228,15 @@ func (v *Muxer) handle(c net.Conn) {
}
type Listener struct {
name string
location string
rewriteHost string
userName string
passWord string
mux *Muxer // for closing Muxer
accept chan net.Conn
ctx context.Context
name string
location string
routeByHTTPUser string
rewriteHost string
userName string
passWord string
mux *Muxer // for closing Muxer
accept chan net.Conn
ctx context.Context
}
func (l *Listener) Accept() (net.Conn, error) {
@@ -231,7 +262,7 @@ func (l *Listener) Accept() (net.Conn, error) {
}
func (l *Listener) Close() error {
l.mux.registryRouter.Del(l.name, l.location)
l.mux.registryRouter.Del(l.name, l.location, l.routeByHTTPUser)
close(l.accept)
return nil
}