Selaa lähdekoodia

Merge pull request #182 from xuebing1110/dev

[dev] http router by host and url
fatedier 8 vuotta sitten
vanhempi
commit
59a34b81e0

+ 9 - 2
src/assets/static/index.html

@@ -25,6 +25,7 @@
                   <th class="tab_info" ng-click="col='current_conns';desc=!desc">CurCon<i class="iconfont pull-right">&#xe66d;</i></th>
                   <th class="tab_info" ng-click="col='daily[daily.length-1].flow_out';desc=!desc">FlowOut<i class="iconfont pull-right">&#xe66d;</i></th>
                   <th class="tab_info" ng-click="col='daily[daily.length-1].flow_in';desc=!desc">FlowIn<i class="iconfont pull-right">&#xe66d;</i></th>
+                  <th class="tab_info" ng-click="col='daily[daily.length-1].total_accept_conns';desc=!desc">TotalAcceptConns<i class="iconfont pull-right">&#xe66d;</i></th>
                 </tr>
               </thead>
               <tbody id="tab_body">
@@ -34,10 +35,11 @@
                   </td>
                   <td><span ng-bind="x.type"></span></td>
                   <td><span ng-bind="x.listen_port"></span></td>
-                  <td><span ng-bind="x.status"></span></td>
+                  <td><span ng-bind="x.status_desc"></span></td>
                   <td><span ng-bind="x.current_conns"></span></td>
                   <td><span ng-bind="x.daily[x.daily.length-1].flow_out"></span></td>
                   <td><span ng-bind="x.daily[x.daily.length-1].flow_in"></span></td>
+                  <td><span ng-bind="x.daily[x.daily.length-1].total_accept_conns"></span></td>
                 </tr>
               </tbody>
             </table>
@@ -63,7 +65,8 @@
         listen_port: "<<< .ListenPort >>>",
         current_conns: <<< .CurrentConns >>> ,
         domains: [ <<< range.CustomDomains >>> "<<< . >>>", <<< end >>> ],
-        stat: "<<< .Status >>>",
+        locations: [ <<< range.Locations >>> "<<< . >>>", <<< end >>> ],
+        stat: "<<< .StatusDesc >>>",
         use_encryption: "<<< .UseEncryption >>>",
         use_gzip: "<<< .UseGzip >>>",
         privilege_mode: "<<< .PrivilegeMode >>>",
@@ -222,6 +225,10 @@
         newrow += "<tr class='info_detail'><td colspan='4'>Domains</td><td colspan='4'>" +
           alldata[index].domains[domainindex] + "</td><tr>";
       }
+      for (var locindex in alldata[index].locations) {
+        newrow += "<tr class='info_detail'><td colspan='4'>Locations</td><td colspan='4'>" +
+          alldata[index].locations[locindex] + "</td><tr>";
+      }
       newrow += "<tr class='info_detail'><td colspan='4'>Ip</td><td colspan='4'>" + alldata[index].bind_addr + "</td><tr>";
       newrow += "<tr class='info_detail'><td colspan='4'>Status</td><td colspan='4'>" + alldata[index].stat + "</td><tr>";
       newrow += "<tr class='info_detail'><td colspan='4'>Encryption</td><td colspan='4'>" + alldata[index].use_encryption + "</td><tr>";

+ 2 - 1
src/cmd/frps/control.go

@@ -332,7 +332,8 @@ func doLogin(req *msg.ControlReq, c *conn.Conn) (ret int64, info string) {
 		}
 
 		// update metric's proxy status
-		metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.ListenPort)
+		//metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip, s.PrivilegeMode, s.CustomDomains, s.ListenPort)
+		metric.SetProxyInfo(*s.ProxyServerConf)
 
 		// start proxy and listen for user connections, no block
 		err := s.Start(c)

+ 22 - 12
src/models/config/config.go

@@ -15,16 +15,26 @@
 package config
 
 type BaseConf struct {
-	Name              string
-	AuthToken         string
-	Type              string
-	UseEncryption     bool
-	UseGzip           bool
-	PrivilegeMode     bool
-	PrivilegeToken    string
-	PoolCount         int64
-	HostHeaderRewrite string
-	HttpUserName      string
-	HttpPassWord      string
-	SubDomain         string
+	Name              string `json:"name"`
+	AuthToken         string `json:"-"`
+	Type              string `json:"type"`
+	UseEncryption     bool   `json:"use_encryption"`
+	UseGzip           bool   `json:"use_gzip"`
+	PrivilegeMode     bool   `json:"privilege_mode"`
+	PrivilegeToken    string `json:"-"`
+	PoolCount         int64  `json:"pool_count"`
+	HostHeaderRewrite string `json:"host_header_rewrite"`
+	HttpUserName      string `json:"http_username"`
+	HttpPassWord      string `json:"-"`
+	SubDomain         string `json:"subdomain"`
+}
+
+type ProxyServerConf struct {
+	BaseConf
+	BindAddr      string   `json:"bind_addr"`
+	ListenPort    int64    `json:"listen_port"`
+	CustomDomains []string `json:"custom_domains"`
+	Locations     []string `json:"custom_locations"`
+
+	Status int64 `json:"status"`
 }

+ 9 - 23
src/models/metric/server.go

@@ -19,6 +19,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/fatedier/frp/src/models/config"
 	"github.com/fatedier/frp/src/models/consts"
 )
 
@@ -29,15 +30,8 @@ var (
 )
 
 type ServerMetric struct {
-	Name          string   `json:"name"`
-	Type          string   `json:"type"`
-	BindAddr      string   `json:"bind_addr"`
-	ListenPort    int64    `json:"listen_port"`
-	CustomDomains []string `json:"custom_domains"`
-	Status        string   `json:"status"`
-	UseEncryption bool     `json:"use_encryption"`
-	UseGzip       bool     `json:"use_gzip"`
-	PrivilegeMode bool     `json:"privilege_mode"`
+	config.ProxyServerConf
+	StatusDesc string `json:"status_desc"`
 
 	// statistics
 	CurrentConns int64               `json:"current_conns"`
@@ -110,24 +104,16 @@ func GetProxyMetrics(proxyName string) *ServerMetric {
 	}
 }
 
-func SetProxyInfo(proxyName string, proxyType, bindAddr string,
-	useEncryption, useGzip, privilegeMode bool, customDomains []string,
-	listenPort int64) {
+func SetProxyInfo(p config.ProxyServerConf) {
 	smMutex.Lock()
-	info, ok := ServerMetricInfoMap[proxyName]
+	info, ok := ServerMetricInfoMap[p.Name]
 	if !ok {
 		info = &ServerMetric{}
 		info.Daily = make([]*DailyServerStats, 0)
 	}
-	info.Name = proxyName
-	info.Type = proxyType
-	info.UseEncryption = useEncryption
-	info.UseGzip = useGzip
-	info.PrivilegeMode = privilegeMode
-	info.BindAddr = bindAddr
-	info.ListenPort = listenPort
-	info.CustomDomains = customDomains
-	ServerMetricInfoMap[proxyName] = info
+
+	info.ProxyServerConf = p
+	ServerMetricInfoMap[p.Name] = info
 	smMutex.Unlock()
 }
 
@@ -137,7 +123,7 @@ func SetStatus(proxyName string, status int64) {
 	smMutex.RUnlock()
 	if ok {
 		metric.mutex.Lock()
-		metric.Status = consts.StatusStr[status]
+		metric.StatusDesc = consts.StatusStr[status]
 		metric.mutex.Unlock()
 	}
 }

+ 11 - 5
src/models/server/config.go

@@ -304,6 +304,14 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
 				} else {
 					return proxyServers, fmt.Errorf("Parse conf error: proxy [%s] custom_domains must be set when type is http", proxyServer.Name)
 				}
+
+				//location
+				locStr, loc_ok := section["custom_location"]
+				if loc_ok {
+					proxyServer.Locations = strings.Split(locStr, ",")
+				} else {
+					proxyServer.Locations = []string{""}
+				}
 			} else if proxyServer.Type == "https" {
 				// for https
 				proxyServer.ListenPort = VhostHttpsPort
@@ -330,9 +338,8 @@ func loadProxyConf(confFile string) (proxyServers map[string]*ProxyServer, err e
 	}
 
 	// set metric statistics of all proxies
-	for name, p := range proxyServers {
-		metric.SetProxyInfo(name, p.Type, p.BindAddr, p.UseEncryption, p.UseGzip,
-			p.PrivilegeMode, p.CustomDomains, p.ListenPort)
+	for _, p := range proxyServers {
+		metric.SetProxyInfo(*p.ProxyServerConf)
 	}
 	return proxyServers, nil
 }
@@ -393,8 +400,7 @@ func CreateProxy(s *ProxyServer) error {
 		}
 	}
 	ProxyServers[s.Name] = s
-	metric.SetProxyInfo(s.Name, s.Type, s.BindAddr, s.UseEncryption, s.UseGzip,
-		s.PrivilegeMode, s.CustomDomains, s.ListenPort)
+	metric.SetProxyInfo(*s.ProxyServerConf)
 	s.Init()
 	return nil
 }

+ 16 - 23
src/models/server/server.go

@@ -35,10 +35,7 @@ type Listener interface {
 }
 
 type ProxyServer struct {
-	config.BaseConf
-	BindAddr      string
-	ListenPort    int64
-	CustomDomains []string
+	*config.ProxyServerConf
 
 	Status      int64
 	CtlConn     *conn.Conn // control connection with frpc
@@ -54,8 +51,9 @@ type ProxyServer struct {
 }
 
 func NewProxyServer() (p *ProxyServer) {
+	psc := &config.ProxyServerConf{CustomDomains: make([]string, 0)}
 	p = &ProxyServer{
-		CustomDomains: make([]string, 0),
+		ProxyServerConf: psc,
 	}
 	return p
 }
@@ -108,6 +106,15 @@ func (p *ProxyServer) Compare(p2 *ProxyServer) bool {
 			return false
 		}
 	}
+
+	if len(p.Locations) != len(p2.Locations) {
+		return false
+	}
+	for i, _ := range p.Locations {
+		if p.Locations[i] != p2.Locations[i] {
+			return false
+		}
+	}
 	return true
 }
 
@@ -130,27 +137,13 @@ func (p *ProxyServer) Start(c *conn.Conn) (err error) {
 		}
 		p.listeners = append(p.listeners, l)
 	} else if p.Type == "http" {
-		for _, domain := range p.CustomDomains {
-			l, err := VhostHttpMuxer.Listen(domain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-			if err != nil {
-				return err
-			}
+		ls := VhostHttpMuxer.Listen(p.ProxyServerConf)
+		for _, l := range ls {
 			p.listeners = append(p.listeners, l)
 		}
-		if p.SubDomain != "" {
-			l, err := VhostHttpMuxer.Listen(p.SubDomain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-			if err != nil {
-				return err
-			}
-			p.listeners = append(p.listeners, l)
-		}
-
 	} else if p.Type == "https" {
-		for _, domain := range p.CustomDomains {
-			l, err := VhostHttpsMuxer.Listen(domain, p.HostHeaderRewrite, p.HttpUserName, p.HttpPassWord)
-			if err != nil {
-				return err
-			}
+		ls := VhostHttpsMuxer.Listen(p.ProxyServerConf)
+		for _, l := range ls {
 			p.listeners = append(p.listeners, l)
 		}
 	}

+ 2 - 0
src/utils/vhost/http.go

@@ -45,6 +45,8 @@ func GetHttpRequestInfo(c *conn.Conn) (_ net.Conn, _ map[string]string, err erro
 	// hostName
 	tmpArr := strings.Split(request.Host, ":")
 	reqInfoMap["Host"] = tmpArr[0]
+	reqInfoMap["Path"] = request.URL.Path
+	reqInfoMap["Scheme"] = request.URL.Scheme
 
 	// Authorization
 	authStr := request.Header.Get("Authorization")

+ 1 - 0
src/utils/vhost/https.go

@@ -186,5 +186,6 @@ func GetHttpsHostname(c *conn.Conn) (sc net.Conn, _ map[string]string, err error
 		return sc, reqInfoMap, err
 	}
 	reqInfoMap["Host"] = host
+	reqInfoMap["Scheme"] = "https"
 	return sc, reqInfoMap, nil
 }

+ 107 - 0
src/utils/vhost/router.go

@@ -0,0 +1,107 @@
+package vhost
+
+import (
+	"sort"
+	"strings"
+	"sync"
+)
+
+type VhostRouters struct {
+	RouterByDomain map[string][]*VhostRouter
+	mutex          sync.RWMutex
+}
+
+type VhostRouter struct {
+	name     string
+	domain   string
+	location string
+	listener *Listener
+}
+
+func NewVhostRouters() *VhostRouters {
+	return &VhostRouters{
+		RouterByDomain: make(map[string][]*VhostRouter),
+	}
+}
+
+//sort by location
+type ByLocation []*VhostRouter
+
+func (a ByLocation) Len() int {
+	return len(a)
+}
+func (a ByLocation) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+func (a ByLocation) Less(i, j int) bool {
+	return strings.Compare(a[i].location, a[j].location) < 0
+}
+
+func (r *VhostRouters) add(name, domain string, locations []string, l *Listener) {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+
+	vrs, found := r.RouterByDomain[domain]
+	if !found {
+		vrs = make([]*VhostRouter, 0)
+	}
+
+	for _, loc := range locations {
+		vr := &VhostRouter{
+			name:     name,
+			domain:   domain,
+			location: loc,
+			listener: l,
+		}
+		vrs = append(vrs, vr)
+	}
+
+	sort.Reverse(ByLocation(vrs))
+	r.RouterByDomain[domain] = vrs
+}
+
+func (r *VhostRouters) del(l *Listener) {
+	r.mutex.Lock()
+	defer r.mutex.Unlock()
+
+	vrs, found := r.RouterByDomain[l.domain]
+	if !found {
+		return
+	}
+
+	for i, vr := range vrs {
+		if vr.listener == l {
+			if len(vrs) > i+1 {
+				r.RouterByDomain[l.domain] = append(vrs[:i], vrs[i+1:]...)
+			} else {
+				r.RouterByDomain[l.domain] = vrs[:i]
+			}
+		}
+	}
+}
+
+func (r *VhostRouters) get(rname string) (vr *VhostRouter, exist bool) {
+	r.mutex.RLock()
+	defer r.mutex.RUnlock()
+
+	var domain, url string
+	tmparray := strings.SplitN(rname, ":", 2)
+	if len(tmparray) == 2 {
+		domain = tmparray[0]
+		url = tmparray[1]
+	}
+
+	vrs, found := r.RouterByDomain[domain]
+	if !found {
+		return
+	}
+
+	//can't support load balance,will to do
+	for _, vr = range vrs {
+		if strings.HasPrefix(url, vr.location) {
+			return vr, true
+		}
+	}
+
+	return
+}

+ 60 - 43
src/utils/vhost/vhost.go

@@ -21,7 +21,9 @@ import (
 	"sync"
 	"time"
 
+	"github.com/fatedier/frp/src/models/config"
 	"github.com/fatedier/frp/src/utils/conn"
+	"github.com/fatedier/frp/src/utils/log"
 )
 
 type muxFunc func(*conn.Conn) (net.Conn, map[string]string, error)
@@ -29,71 +31,86 @@ type httpAuthFunc func(*conn.Conn, string, string, string) (bool, error)
 type hostRewriteFunc func(*conn.Conn, string) (net.Conn, error)
 
 type VhostMuxer struct {
-	listener    *conn.Listener
-	timeout     time.Duration
-	vhostFunc   muxFunc
-	authFunc    httpAuthFunc
-	rewriteFunc hostRewriteFunc
-	registryMap map[string]*Listener
-	mutex       sync.RWMutex
+	listener       *conn.Listener
+	timeout        time.Duration
+	vhostFunc      muxFunc
+	authFunc       httpAuthFunc
+	rewriteFunc    hostRewriteFunc
+	registryRouter *VhostRouters
+	mutex          sync.RWMutex
 }
 
 func NewVhostMuxer(listener *conn.Listener, vhostFunc muxFunc, authFunc httpAuthFunc, rewriteFunc hostRewriteFunc, timeout time.Duration) (mux *VhostMuxer, err error) {
 	mux = &VhostMuxer{
-		listener:    listener,
-		timeout:     timeout,
-		vhostFunc:   vhostFunc,
-		authFunc:    authFunc,
-		rewriteFunc: rewriteFunc,
-		registryMap: make(map[string]*Listener),
+		listener:       listener,
+		timeout:        timeout,
+		vhostFunc:      vhostFunc,
+		authFunc:       authFunc,
+		rewriteFunc:    rewriteFunc,
+		registryRouter: NewVhostRouters(),
 	}
 	go mux.run()
 	return mux, nil
 }
 
 // listen for a new domain name, if rewriteHost is not empty  and rewriteFunc is not nil, then rewrite the host header to rewriteHost
-func (v *VhostMuxer) Listen(name string, rewriteHost, userName, passWord string) (l *Listener, err error) {
+func (v *VhostMuxer) Listen(p *config.ProxyServerConf) (ls []*Listener) {
 	v.mutex.Lock()
 	defer v.mutex.Unlock()
-	if _, exist := v.registryMap[name]; exist {
-		return nil, fmt.Errorf("domain name %s is already bound", name)
-	}
 
-	l = &Listener{
-		name:        name,
-		rewriteHost: rewriteHost,
-		userName:    userName,
-		passWord:    passWord,
-		mux:         v,
-		accept:      make(chan *conn.Conn),
+	ls = make([]*Listener, 0)
+	for _, domain := range p.CustomDomains {
+		l := &Listener{
+			name:        p.Name,
+			domain:      domain,
+			locations:   p.Locations,
+			rewriteHost: p.HostHeaderRewrite,
+			userName:    p.HttpUserName,
+			passWord:    p.HttpPassWord,
+			mux:         v,
+			accept:      make(chan *conn.Conn),
+		}
+		v.registryRouter.add(p.Name, domain, p.Locations, l)
+		ls = append(ls, l)
 	}
-	v.registryMap[name] = l
-	return l, nil
+	return ls
 }
 
-func (v *VhostMuxer) getListener(name string) (l *Listener, exist bool) {
+func (v *VhostMuxer) getListener(reqInfoMap map[string]string) (l *Listener, exist bool) {
 	v.mutex.RLock()
 	defer v.mutex.RUnlock()
-	// first we check the full hostname
-	// if not exist, then check the wildcard_domain such as *.example.com
-	l, exist = v.registryMap[name]
-	if exist {
-		return l, exist
+
+	//host
+	name := strings.ToLower(reqInfoMap["Host"])
+
+	// http
+	scheme := strings.ToLower(reqInfoMap["Scheme"])
+	if scheme == "http" || scheme == "" {
+		name = name + ":" + reqInfoMap["Path"]
+	}
+
+	// // first we check the full hostname
+	vr, found := v.registryRouter.get(name)
+	if found {
+		return vr.listener, true
 	}
+
+	//if not exist, then check the wildcard_domain such as *.example.com
 	domainSplit := strings.Split(name, ".")
 	if len(domainSplit) < 3 {
+		log.Warn("can't found the router for %s", name)
 		return l, false
 	}
 	domainSplit[0] = "*"
 	name = strings.Join(domainSplit, ".")
-	l, exist = v.registryMap[name]
-	return l, exist
-}
 
-func (v *VhostMuxer) unRegister(name string) {
-	v.mutex.Lock()
-	defer v.mutex.Unlock()
-	delete(v.registryMap, name)
+	vr, found = v.registryRouter.get(name)
+	if !found {
+		log.Warn("can't found the router for %s", name)
+		return
+	}
+
+	return vr.listener, true
 }
 
 func (v *VhostMuxer) run() {
@@ -118,9 +135,7 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 		return
 	}
 
-	name := strings.ToLower(reqInfoMap["Host"])
-	// get listener by hostname
-	l, ok := v.getListener(name)
+	l, ok := v.getListener(reqInfoMap)
 	if !ok {
 		c.Close()
 		return
@@ -150,6 +165,8 @@ func (v *VhostMuxer) handle(c *conn.Conn) {
 
 type Listener struct {
 	name        string
+	domain      string
+	locations   []string
 	rewriteHost string
 	userName    string
 	passWord    string
@@ -177,7 +194,7 @@ func (l *Listener) Accept() (*conn.Conn, error) {
 }
 
 func (l *Listener) Close() error {
-	l.mux.unRegister(l.name)
+	l.mux.registryRouter.del(l)
 	close(l.accept)
 	return nil
 }