Browse Source

merged rate limit

Toby Chui 9 months ago
parent
commit
f788edde19

+ 8 - 0
mod/dynamicproxy/Server.go

@@ -72,6 +72,14 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
+		// Rate Limit Check
+		if sep.RequireRateLimit {
+			err := h.handleRateLimitRouting(w, r, sep)
+			if err != nil {
+				return
+			}
+		}
+
 		//Validate basic auth
 		if sep.RequireBasicAuth {
 			err := h.handleBasicAuthRouting(w, r, sep)

+ 346 - 339
mod/dynamicproxy/dynamicproxy.go

@@ -1,339 +1,346 @@
-package dynamicproxy
-
-import (
-	"context"
-	"crypto/tls"
-	"encoding/json"
-	"errors"
-	"log"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
-
-	"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
-)
-
-/*
-	Zoraxy Dynamic Proxy
-*/
-
-func NewDynamicProxy(option RouterOption) (*Router, error) {
-	proxyMap := sync.Map{}
-	thisRouter := Router{
-		Option:         &option,
-		ProxyEndpoints: &proxyMap,
-		Running:        false,
-		server:         nil,
-		routingRules:   []*RoutingRule{},
-		tldMap:         map[string]int{},
-	}
-
-	thisRouter.mux = &ProxyHandler{
-		Parent: &thisRouter,
-	}
-
-	return &thisRouter, nil
-}
-
-// Update TLS setting in runtime. Will restart the proxy server
-// if it is already running in the background
-func (router *Router) UpdateTLSSetting(tlsEnabled bool) {
-	router.Option.UseTls = tlsEnabled
-	router.Restart()
-}
-
-// Update TLS Version in runtime. Will restart proxy server if running.
-// Set this to true to force TLS 1.2 or above
-func (router *Router) UpdateTLSVersion(requireLatest bool) {
-	router.Option.ForceTLSLatest = requireLatest
-	router.Restart()
-}
-
-// Update port 80 listener state
-func (router *Router) UpdatePort80ListenerState(useRedirect bool) {
-	router.Option.ListenOnPort80 = useRedirect
-	router.Restart()
-}
-
-// Update https redirect, which will require updates
-func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
-	router.Option.ForceHttpsRedirect = useRedirect
-	router.Restart()
-}
-
-// Start the dynamic routing
-func (router *Router) StartProxyService() error {
-	//Create a new server object
-	if router.server != nil {
-		return errors.New("reverse proxy server already running")
-	}
-
-	//Check if root route is set
-	if router.Root == nil {
-		return errors.New("reverse proxy router root not set")
-	}
-
-	minVersion := tls.VersionTLS10
-	if router.Option.ForceTLSLatest {
-		minVersion = tls.VersionTLS12
-	}
-	config := &tls.Config{
-		GetCertificate: router.Option.TlsManager.GetCert,
-		MinVersion:     uint16(minVersion),
-	}
-
-	if router.Option.UseTls {
-		router.server = &http.Server{
-			Addr:      ":" + strconv.Itoa(router.Option.Port),
-			Handler:   router.mux,
-			TLSConfig: config,
-		}
-		router.Running = true
-
-		if router.Option.Port != 80 && router.Option.ListenOnPort80 {
-			//Add a 80 to 443 redirector
-			httpServer := &http.Server{
-				Addr: ":80",
-				Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-					//Check if the domain requesting allow non TLS mode
-					domainOnly := r.Host
-					if strings.Contains(r.Host, ":") {
-						hostPath := strings.Split(r.Host, ":")
-						domainOnly = hostPath[0]
-					}
-					sep := router.getProxyEndpointFromHostname(domainOnly)
-					if sep != nil && sep.BypassGlobalTLS {
-						//Allow routing via non-TLS handler
-						originalHostHeader := r.Host
-						if r.URL != nil {
-							r.Host = r.URL.Host
-						} else {
-							//Fallback when the upstream proxy screw something up in the header
-							r.URL, _ = url.Parse(originalHostHeader)
-						}
-
-						//Access Check (blacklist / whitelist)
-						ruleID := sep.AccessFilterUUID
-						if sep.AccessFilterUUID == "" {
-							//Use default rule
-							ruleID = "default"
-						}
-						accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
-						if err == nil {
-							isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
-							if isBlocked {
-								return
-							}
-						}
-
-						//Validate basic auth
-						if sep.RequireBasicAuth {
-							err := handleBasicAuth(w, r, sep)
-							if err != nil {
-								return
-							}
-						}
-
-						sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
-							ProxyDomain:  sep.Domain,
-							OriginalHost: originalHostHeader,
-							UseTLS:       sep.RequireTLS,
-							PathPrefix:   "",
-							Version:      sep.parent.Option.HostVersion,
-						})
-						return
-					}
-
-					if router.Option.ForceHttpsRedirect {
-						//Redirect to https is enabled
-						protocol := "https://"
-						if router.Option.Port == 443 {
-							http.Redirect(w, r, protocol+r.Host+r.RequestURI, http.StatusTemporaryRedirect)
-						} else {
-							http.Redirect(w, r, protocol+r.Host+":"+strconv.Itoa(router.Option.Port)+r.RequestURI, http.StatusTemporaryRedirect)
-						}
-					} else {
-						//Do not do redirection
-						if sep != nil {
-							//Sub-domain exists but not allow non-TLS access
-							w.WriteHeader(http.StatusBadRequest)
-							w.Write([]byte("400 - Bad Request"))
-						} else {
-							//No defined sub-domain
-							http.NotFound(w, r)
-						}
-
-					}
-
-				}),
-				ReadTimeout:  3 * time.Second,
-				WriteTimeout: 3 * time.Second,
-				IdleTimeout:  120 * time.Second,
-			}
-
-			log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
-
-			//Create a redirection stop channel
-			stopChan := make(chan bool)
-
-			//Start a blocking wait for shutting down the http to https redirection server
-			go func() {
-				<-stopChan
-				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-				defer cancel()
-				httpServer.Shutdown(ctx)
-				log.Println("HTTP to HTTPS redirection listener stopped")
-			}()
-
-			//Start the http server that listens to port 80 and redirect to 443
-			go func() {
-				if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
-					//Unable to startup port 80 listener. Handle shutdown process gracefully
-					stopChan <- true
-					log.Fatalf("Could not start redirection server: %v\n", err)
-				}
-			}()
-			router.tlsRedirectStop = stopChan
-		}
-
-		//Start the TLS server
-		log.Println("Reverse proxy service started in the background (TLS mode)")
-		go func() {
-			if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
-				log.Fatalf("Could not start proxy server: %v\n", err)
-			}
-		}()
-	} else {
-		//Serve with non TLS mode
-		router.tlsListener = nil
-		router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
-		router.Running = true
-		log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
-		go func() {
-			router.server.ListenAndServe()
-			//log.Println("[DynamicProxy] " + err.Error())
-		}()
-	}
-
-	return nil
-}
-
-func (router *Router) StopProxyService() error {
-	if router.server == nil {
-		return errors.New("reverse proxy server already stopped")
-	}
-	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-	defer cancel()
-	err := router.server.Shutdown(ctx)
-	if err != nil {
-		return err
-	}
-
-	if router.tlsListener != nil {
-		router.tlsListener.Close()
-	}
-
-	if router.tlsRedirectStop != nil {
-		router.tlsRedirectStop <- true
-	}
-
-	//Discard the server object
-	router.tlsListener = nil
-	router.server = nil
-	router.Running = false
-	router.tlsRedirectStop = nil
-	return nil
-}
-
-// Restart the current router if it is running.
-func (router *Router) Restart() error {
-	//Stop the router if it is already running
-	if router.Running {
-		err := router.StopProxyService()
-		if err != nil {
-			return err
-		}
-
-		time.Sleep(300 * time.Millisecond)
-		// Start the server
-		err = router.StartProxyService()
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-/*
-	Check if a given request is accessed via a proxied subdomain
-*/
-
-func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
-	hostname := r.Header.Get("X-Forwarded-Host")
-	if hostname == "" {
-		hostname = r.Host
-	}
-	hostname = strings.Split(hostname, ":")[0]
-	subdEndpoint := router.getProxyEndpointFromHostname(hostname)
-	return subdEndpoint != nil
-}
-
-/*
-Load routing from RP
-*/
-func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) {
-	var targetProxyEndpoint *ProxyEndpoint
-	router.ProxyEndpoints.Range(func(key, value interface{}) bool {
-		key, ok := key.(string)
-		if !ok {
-			return true
-		}
-		v, ok := value.(*ProxyEndpoint)
-		if !ok {
-			return true
-		}
-
-		if key == matchingDomain {
-			targetProxyEndpoint = v
-		}
-		return true
-	})
-
-	if targetProxyEndpoint == nil {
-		return nil, errors.New("target routing rule not found")
-	}
-
-	return targetProxyEndpoint, nil
-}
-
-// Deep copy a proxy endpoint, excluding runtime paramters
-func CopyEndpoint(endpoint *ProxyEndpoint) *ProxyEndpoint {
-	js, _ := json.Marshal(endpoint)
-	newProxyEndpoint := ProxyEndpoint{}
-	err := json.Unmarshal(js, &newProxyEndpoint)
-	if err != nil {
-		return nil
-	}
-	return &newProxyEndpoint
-}
-
-func (r *Router) GetProxyEndpointsAsMap() map[string]*ProxyEndpoint {
-	m := make(map[string]*ProxyEndpoint)
-	r.ProxyEndpoints.Range(func(key, value interface{}) bool {
-		k, ok := key.(string)
-		if !ok {
-			return true
-		}
-		v, ok := value.(*ProxyEndpoint)
-		if !ok {
-			return true
-		}
-		m[k] = v
-		return true
-	})
-	return m
-}
+package dynamicproxy
+
+import (
+	"context"
+	"crypto/tls"
+	"encoding/json"
+	"errors"
+	"log"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
+)
+
+/*
+	Zoraxy Dynamic Proxy
+*/
+
+func NewDynamicProxy(option RouterOption) (*Router, error) {
+	proxyMap := sync.Map{}
+	thisRouter := Router{
+		Option:         &option,
+		ProxyEndpoints: &proxyMap,
+		Running:        false,
+		server:         nil,
+		routingRules:   []*RoutingRule{},
+		tldMap:         map[string]int{},
+	}
+
+	thisRouter.mux = &ProxyHandler{
+		Parent: &thisRouter,
+	}
+
+	return &thisRouter, nil
+}
+
+// Update TLS setting in runtime. Will restart the proxy server
+// if it is already running in the background
+func (router *Router) UpdateTLSSetting(tlsEnabled bool) {
+	router.Option.UseTls = tlsEnabled
+	router.Restart()
+}
+
+// Update TLS Version in runtime. Will restart proxy server if running.
+// Set this to true to force TLS 1.2 or above
+func (router *Router) UpdateTLSVersion(requireLatest bool) {
+	router.Option.ForceTLSLatest = requireLatest
+	router.Restart()
+}
+
+// Update port 80 listener state
+func (router *Router) UpdatePort80ListenerState(useRedirect bool) {
+	router.Option.ListenOnPort80 = useRedirect
+	router.Restart()
+}
+
+// Update https redirect, which will require updates
+func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
+	router.Option.ForceHttpsRedirect = useRedirect
+	router.Restart()
+}
+
+// Start the dynamic routing
+func (router *Router) StartProxyService() error {
+	//Create a new server object
+	if router.server != nil {
+		return errors.New("reverse proxy server already running")
+	}
+
+	//Check if root route is set
+	if router.Root == nil {
+		return errors.New("reverse proxy router root not set")
+	}
+
+	minVersion := tls.VersionTLS10
+	if router.Option.ForceTLSLatest {
+		minVersion = tls.VersionTLS12
+	}
+	config := &tls.Config{
+		GetCertificate: router.Option.TlsManager.GetCert,
+		MinVersion:     uint16(minVersion),
+	}
+
+	if router.Option.UseTls {
+		router.server = &http.Server{
+			Addr:      ":" + strconv.Itoa(router.Option.Port),
+			Handler:   router.mux,
+			TLSConfig: config,
+		}
+		router.Running = true
+
+		if router.Option.Port != 80 && router.Option.ListenOnPort80 {
+			//Add a 80 to 443 redirector
+			httpServer := &http.Server{
+				Addr: ":80",
+				Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					//Check if the domain requesting allow non TLS mode
+					domainOnly := r.Host
+					if strings.Contains(r.Host, ":") {
+						hostPath := strings.Split(r.Host, ":")
+						domainOnly = hostPath[0]
+					}
+					sep := router.getProxyEndpointFromHostname(domainOnly)
+					if sep != nil && sep.BypassGlobalTLS {
+						//Allow routing via non-TLS handler
+						originalHostHeader := r.Host
+						if r.URL != nil {
+							r.Host = r.URL.Host
+						} else {
+							//Fallback when the upstream proxy screw something up in the header
+							r.URL, _ = url.Parse(originalHostHeader)
+						}
+
+						//Access Check (blacklist / whitelist)
+						ruleID := sep.AccessFilterUUID
+						if sep.AccessFilterUUID == "" {
+							//Use default rule
+							ruleID = "default"
+						}
+						accessRule, err := router.Option.AccessController.GetAccessRuleByID(ruleID)
+						if err == nil {
+							isBlocked, _ := accessRequestBlocked(accessRule, router.Option.WebDirectory, w, r)
+							if isBlocked {
+								return
+							}
+						}
+
+						// Rate Limit Check
+						// if sep.RequireBasicAuth {
+						if err := handleRateLimit(w, r, sep); err != nil {
+							return
+						}
+						// }
+
+						//Validate basic auth
+						if sep.RequireBasicAuth {
+							err := handleBasicAuth(w, r, sep)
+							if err != nil {
+								return
+							}
+						}
+
+						sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
+							ProxyDomain:  sep.Domain,
+							OriginalHost: originalHostHeader,
+							UseTLS:       sep.RequireTLS,
+							PathPrefix:   "",
+							Version:      sep.parent.Option.HostVersion,
+						})
+						return
+					}
+
+					if router.Option.ForceHttpsRedirect {
+						//Redirect to https is enabled
+						protocol := "https://"
+						if router.Option.Port == 443 {
+							http.Redirect(w, r, protocol+r.Host+r.RequestURI, http.StatusTemporaryRedirect)
+						} else {
+							http.Redirect(w, r, protocol+r.Host+":"+strconv.Itoa(router.Option.Port)+r.RequestURI, http.StatusTemporaryRedirect)
+						}
+					} else {
+						//Do not do redirection
+						if sep != nil {
+							//Sub-domain exists but not allow non-TLS access
+							w.WriteHeader(http.StatusBadRequest)
+							w.Write([]byte("400 - Bad Request"))
+						} else {
+							//No defined sub-domain
+							http.NotFound(w, r)
+						}
+
+					}
+
+				}),
+				ReadTimeout:  3 * time.Second,
+				WriteTimeout: 3 * time.Second,
+				IdleTimeout:  120 * time.Second,
+			}
+
+			log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
+
+			//Create a redirection stop channel
+			stopChan := make(chan bool)
+
+			//Start a blocking wait for shutting down the http to https redirection server
+			go func() {
+				<-stopChan
+				ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+				defer cancel()
+				httpServer.Shutdown(ctx)
+				log.Println("HTTP to HTTPS redirection listener stopped")
+			}()
+
+			//Start the http server that listens to port 80 and redirect to 443
+			go func() {
+				if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+					//Unable to startup port 80 listener. Handle shutdown process gracefully
+					stopChan <- true
+					log.Fatalf("Could not start redirection server: %v\n", err)
+				}
+			}()
+			router.tlsRedirectStop = stopChan
+		}
+
+		//Start the TLS server
+		log.Println("Reverse proxy service started in the background (TLS mode)")
+		go func() {
+			if err := router.server.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
+				log.Fatalf("Could not start proxy server: %v\n", err)
+			}
+		}()
+	} else {
+		//Serve with non TLS mode
+		router.tlsListener = nil
+		router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
+		router.Running = true
+		log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
+		go func() {
+			router.server.ListenAndServe()
+			//log.Println("[DynamicProxy] " + err.Error())
+		}()
+	}
+
+	return nil
+}
+
+func (router *Router) StopProxyService() error {
+	if router.server == nil {
+		return errors.New("reverse proxy server already stopped")
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	err := router.server.Shutdown(ctx)
+	if err != nil {
+		return err
+	}
+
+	if router.tlsListener != nil {
+		router.tlsListener.Close()
+	}
+
+	if router.tlsRedirectStop != nil {
+		router.tlsRedirectStop <- true
+	}
+
+	//Discard the server object
+	router.tlsListener = nil
+	router.server = nil
+	router.Running = false
+	router.tlsRedirectStop = nil
+	return nil
+}
+
+// Restart the current router if it is running.
+func (router *Router) Restart() error {
+	//Stop the router if it is already running
+	if router.Running {
+		err := router.StopProxyService()
+		if err != nil {
+			return err
+		}
+
+		time.Sleep(300 * time.Millisecond)
+		// Start the server
+		err = router.StartProxyService()
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+/*
+	Check if a given request is accessed via a proxied subdomain
+*/
+
+func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
+	hostname := r.Header.Get("X-Forwarded-Host")
+	if hostname == "" {
+		hostname = r.Host
+	}
+	hostname = strings.Split(hostname, ":")[0]
+	subdEndpoint := router.getProxyEndpointFromHostname(hostname)
+	return subdEndpoint != nil
+}
+
+/*
+Load routing from RP
+*/
+func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) {
+	var targetProxyEndpoint *ProxyEndpoint
+	router.ProxyEndpoints.Range(func(key, value interface{}) bool {
+		key, ok := key.(string)
+		if !ok {
+			return true
+		}
+		v, ok := value.(*ProxyEndpoint)
+		if !ok {
+			return true
+		}
+
+		if key == matchingDomain {
+			targetProxyEndpoint = v
+		}
+		return true
+	})
+
+	if targetProxyEndpoint == nil {
+		return nil, errors.New("target routing rule not found")
+	}
+
+	return targetProxyEndpoint, nil
+}
+
+// Deep copy a proxy endpoint, excluding runtime paramters
+func CopyEndpoint(endpoint *ProxyEndpoint) *ProxyEndpoint {
+	js, _ := json.Marshal(endpoint)
+	newProxyEndpoint := ProxyEndpoint{}
+	err := json.Unmarshal(js, &newProxyEndpoint)
+	if err != nil {
+		return nil
+	}
+	return &newProxyEndpoint
+}
+
+func (r *Router) GetProxyEndpointsAsMap() map[string]*ProxyEndpoint {
+	m := make(map[string]*ProxyEndpoint)
+	r.ProxyEndpoints.Range(func(key, value interface{}) bool {
+		k, ok := key.(string)
+		if !ok {
+			return true
+		}
+		v, ok := value.(*ProxyEndpoint)
+		if !ok {
+			return true
+		}
+		m[k] = v
+		return true
+	})
+	return m
+}

+ 86 - 0
mod/dynamicproxy/ratelimit.go

@@ -0,0 +1,86 @@
+package dynamicproxy
+
+import (
+	"errors"
+	"log"
+	"net"
+	"net/http"
+	"sync"
+	"sync/atomic"
+	"time"
+)
+
+// IpTable is a rate limiter implementation using sync.Map with atomic int64
+type IpTable struct {
+	table sync.Map
+}
+
+// Increment the count of requests for a given IP
+func (t *IpTable) Increment(ip string) {
+	v, _ := t.table.LoadOrStore(ip, new(int64))
+	atomic.AddInt64(v.(*int64), 1)
+}
+
+// Check if the IP is in the table and if it is, check if the count is less than the limit
+func (t *IpTable) Exceeded(ip string, limit int64) bool {
+	v, ok := t.table.Load(ip)
+	if !ok {
+		return false
+	}
+	count := atomic.LoadInt64(v.(*int64))
+	return count >= limit
+}
+
+// Get the count of requests for a given IP
+func (t *IpTable) GetCount(ip string) int64 {
+	v, ok := t.table.Load(ip)
+	if !ok {
+		return 0
+	}
+	return atomic.LoadInt64(v.(*int64))
+}
+
+// Clear the IP table
+func (t *IpTable) Clear() {
+	t.table.Range(func(key, value interface{}) bool {
+		t.table.Delete(key)
+		return true
+	})
+}
+
+var ipTable = IpTable{}
+
+func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
+	err := handleRateLimit(w, r, pe)
+	if err != nil {
+		h.logRequest(r, false, 429, "ratelimit", pe.Domain)
+	}
+	return err
+}
+
+func handleRateLimit(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
+	ip, _, err := net.SplitHostPort(r.RemoteAddr)
+	if err != nil {
+		w.WriteHeader(500)
+		log.Println("Error resolving remote address", r.RemoteAddr, err)
+		return errors.New("internal server error")
+	}
+
+	ipTable.Increment(ip)
+
+	if ipTable.Exceeded(ip, int64(pe.RateLimit)) {
+		w.WriteHeader(429)
+		return errors.New("rate limit exceeded")
+	}
+
+	// log.Println("Rate limit check", ip, ipTable.GetCount(ip))
+
+	return nil
+}
+
+func InitRateLimit() {
+	for {
+		ipTable.Clear()
+		time.Sleep(time.Second)
+	}
+}

+ 4 - 0
mod/dynamicproxy/typedef.go

@@ -124,6 +124,10 @@ type ProxyEndpoint struct {
 	BasicAuthCredentials    []*BasicAuthCredentials   //Basic auth credentials
 	BasicAuthExceptionRules []*BasicAuthExceptionRule //Path to exclude in a basic auth enabled proxy target
 
+	// Rate Limiting
+	RequireRateLimit bool
+	RateLimit        int64 // Rate limit in requests per second
+
 	//Access Control
 	AccessFilterUUID string //Access filter ID
 

+ 1238 - 1189
reverseproxy.go

@@ -1,1189 +1,1238 @@
-package main
-
-import (
-	"encoding/json"
-	"net/http"
-	"path/filepath"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
-	"imuslab.com/zoraxy/mod/auth"
-	"imuslab.com/zoraxy/mod/dynamicproxy"
-	"imuslab.com/zoraxy/mod/uptime"
-	"imuslab.com/zoraxy/mod/utils"
-)
-
-var (
-	dynamicProxyRouter *dynamicproxy.Router
-)
-
-// Add user customizable reverse proxy
-func ReverseProxtInit() {
-	/*
-		Load Reverse Proxy Global Settings
-	*/
-	inboundPort := 80
-	if sysdb.KeyExists("settings", "inbound") {
-		sysdb.Read("settings", "inbound", &inboundPort)
-		SystemWideLogger.Println("Serving inbound port ", inboundPort)
-	} else {
-		SystemWideLogger.Println("Inbound port not set. Using default (80)")
-	}
-
-	useTls := false
-	sysdb.Read("settings", "usetls", &useTls)
-	if useTls {
-		SystemWideLogger.Println("TLS mode enabled. Serving proxxy request with TLS")
-	} else {
-		SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
-	}
-
-	forceLatestTLSVersion := false
-	sysdb.Read("settings", "forceLatestTLS", &forceLatestTLSVersion)
-	if forceLatestTLSVersion {
-		SystemWideLogger.Println("Force latest TLS mode enabled. Minimum TLS LS version is set to v1.2")
-	} else {
-		SystemWideLogger.Println("Force latest TLS mode disabled. Minimum TLS version is set to v1.0")
-	}
-
-	developmentMode := false
-	sysdb.Read("settings", "devMode", &developmentMode)
-	if useTls {
-		SystemWideLogger.Println("Development mode enabled. Using no-store Cache Control policy")
-	} else {
-		SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
-	}
-
-	listenOnPort80 := false
-	sysdb.Read("settings", "listenP80", &listenOnPort80)
-	if listenOnPort80 {
-		SystemWideLogger.Println("Port 80 listener enabled")
-	} else {
-		SystemWideLogger.Println("Port 80 listener disabled")
-	}
-
-	forceHttpsRedirect := false
-	sysdb.Read("settings", "redirect", &forceHttpsRedirect)
-	if forceHttpsRedirect {
-		SystemWideLogger.Println("Force HTTPS mode enabled")
-		//Port 80 listener must be enabled to perform http -> https redirect
-		listenOnPort80 = true
-	} else {
-		SystemWideLogger.Println("Force HTTPS mode disabled")
-	}
-
-	/*
-		Create a new proxy object
-		The DynamicProxy is the parent of all reverse proxy handlers,
-		use for managemening and provide functions to access proxy handlers
-	*/
-
-	dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
-		HostUUID:           nodeUUID,
-		HostVersion:        version,
-		Port:               inboundPort,
-		UseTls:             useTls,
-		ForceTLSLatest:     forceLatestTLSVersion,
-		NoCache:            developmentMode,
-		ListenOnPort80:     listenOnPort80,
-		ForceHttpsRedirect: forceHttpsRedirect,
-		TlsManager:         tlsCertManager,
-		RedirectRuleTable:  redirectTable,
-		GeodbStore:         geodbStore,
-		StatisticCollector: statisticCollector,
-		WebDirectory:       *staticWebServerRoot,
-		AccessController:   accessController,
-	})
-	if err != nil {
-		SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
-		return
-	}
-
-	dynamicProxyRouter = dprouter
-
-	/*
-
-		Load all conf from files
-
-	*/
-	confs, _ := filepath.Glob("./conf/proxy/*.config")
-	for _, conf := range confs {
-		err := LoadReverseProxyConfig(conf)
-		if err != nil {
-			SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err)
-			return
-		}
-	}
-
-	if dynamicProxyRouter.Root == nil {
-		//Root config not set (new deployment?), use internal static web server as root
-		defaultRootRouter, err := GetDefaultRootConfig()
-		if err != nil {
-			SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err)
-			return
-		}
-		dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
-	}
-
-	//Start Service
-	//Not sure why but delay must be added if you have another
-	//reverse proxy server in front of this service
-	time.Sleep(300 * time.Millisecond)
-	dynamicProxyRouter.StartProxyService()
-	SystemWideLogger.Println("Dynamic Reverse Proxy service started")
-
-	//Add all proxy services to uptime monitor
-	//Create a uptime monitor service
-	go func() {
-		//This must be done in go routine to prevent blocking on system startup
-		uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{
-			Targets:         GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
-			Interval:        300, //5 minutes
-			MaxRecordsStore: 288, //1 day
-		})
-		SystemWideLogger.Println("Uptime Monitor background service started")
-	}()
-
-}
-
-func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
-	enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
-	if enable == "true" {
-		err := dynamicProxyRouter.StartProxyService()
-		if err != nil {
-			utils.SendErrorResponse(w, err.Error())
-			return
-		}
-	} else {
-		//Check if it is loopback
-		if dynamicProxyRouter.IsProxiedSubdomain(r) {
-			//Loopback routing. Turning it off will make the user lost control
-			//of the whole system. Do not allow shutdown
-			utils.SendErrorResponse(w, "Unable to shutdown in loopback rp mode. Remove proxy rules for management interface and retry.")
-			return
-		}
-
-		err := dynamicProxyRouter.StopProxyService()
-		if err != nil {
-			utils.SendErrorResponse(w, err.Error())
-			return
-		}
-	}
-
-	utils.SendOK(w)
-}
-
-func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
-	eptype, err := utils.PostPara(r, "type") //Support root and host
-	if err != nil {
-		utils.SendErrorResponse(w, "type not defined")
-		return
-	}
-
-	endpoint, err := utils.PostPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "endpoint not defined")
-		return
-	}
-
-	tls, _ := utils.PostPara(r, "tls")
-	if tls == "" {
-		tls = "false"
-	}
-
-	useTLS := (tls == "true")
-
-	//Bypass global TLS value / allow direct access from port 80?
-	bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
-	if bypassGlobalTLS == "" {
-		bypassGlobalTLS = "false"
-	}
-
-	useBypassGlobalTLS := bypassGlobalTLS == "true"
-
-	//Enable TLS validation?
-	stv, _ := utils.PostPara(r, "tlsval")
-	if stv == "" {
-		stv = "false"
-	}
-
-	skipTlsValidation := (stv == "true")
-
-	//Get access rule ID
-	accessRuleID, _ := utils.PostPara(r, "access")
-	if accessRuleID == "" {
-		accessRuleID = "default"
-	}
-	if !accessController.AccessRuleExists(accessRuleID) {
-		utils.SendErrorResponse(w, "invalid access rule ID selected")
-		return
-	}
-
-	//Require basic auth?
-	rba, _ := utils.PostPara(r, "bauth")
-	if rba == "" {
-		rba = "false"
-	}
-
-	requireBasicAuth := (rba == "true")
-
-	// Bypass WebSocket Origin Check
-	strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
-	if strbpwsorg == "" {
-		strbpwsorg = "false"
-	}
-	bypassWebsocketOriginCheck := (strbpwsorg == "true")
-
-	//Prase the basic auth to correct structure
-	cred, _ := utils.PostPara(r, "cred")
-	basicAuthCredentials := []*dynamicproxy.BasicAuthCredentials{}
-	if requireBasicAuth {
-		preProcessCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
-		err = json.Unmarshal([]byte(cred), &preProcessCredentials)
-		if err != nil {
-			utils.SendErrorResponse(w, "invalid user credentials")
-			return
-		}
-
-		//Check if there are empty password credentials
-		for _, credObj := range preProcessCredentials {
-			if strings.TrimSpace(credObj.Password) == "" {
-				utils.SendErrorResponse(w, credObj.Username+" has empty password")
-				return
-			}
-		}
-
-		//Convert and hash the passwords
-		for _, credObj := range preProcessCredentials {
-			basicAuthCredentials = append(basicAuthCredentials, &dynamicproxy.BasicAuthCredentials{
-				Username:     credObj.Username,
-				PasswordHash: auth.Hash(credObj.Password),
-			})
-		}
-	}
-
-	var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
-	if eptype == "host" {
-		rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
-		if err != nil {
-			utils.SendErrorResponse(w, "hostname not defined")
-			return
-		}
-		rootOrMatchingDomain = strings.TrimSpace(rootOrMatchingDomain)
-
-		//Check if it contains ",", if yes, split the remainings as alias
-		aliasHostnames := []string{}
-		if strings.Contains(rootOrMatchingDomain, ",") {
-			matchingDomains := strings.Split(rootOrMatchingDomain, ",")
-			if len(matchingDomains) > 1 {
-				rootOrMatchingDomain = matchingDomains[0]
-				for _, aliasHostname := range matchingDomains[1:] {
-					//Filter out any space
-					aliasHostnames = append(aliasHostnames, strings.TrimSpace(aliasHostname))
-				}
-			}
-		}
-
-		//Generate a proxy endpoint object
-		thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
-			//I/O
-			ProxyType:            dynamicproxy.ProxyType_Host,
-			RootOrMatchingDomain: rootOrMatchingDomain,
-			MatchingDomainAlias:  aliasHostnames,
-			Domain:               endpoint,
-			//TLS
-			RequireTLS:               useTLS,
-			BypassGlobalTLS:          useBypassGlobalTLS,
-			SkipCertValidations:      skipTlsValidation,
-			SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
-			AccessFilterUUID:         accessRuleID,
-			//VDir
-			VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
-			//Custom headers
-			UserDefinedHeaders: []*dynamicproxy.UserDefinedHeader{},
-			//Auth
-			RequireBasicAuth:        requireBasicAuth,
-			BasicAuthCredentials:    basicAuthCredentials,
-			BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
-			DefaultSiteOption:       0,
-			DefaultSiteValue:        "",
-		}
-
-		preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
-		if err != nil {
-			utils.SendErrorResponse(w, "unable to prepare proxy route to target endpoint: "+err.Error())
-			return
-		}
-
-		dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
-		proxyEndpointCreated = &thisProxyEndpoint
-	} else if eptype == "root" {
-		//Get the default site options and target
-		dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
-		if err != nil {
-			utils.SendErrorResponse(w, "default site action not defined")
-			return
-		}
-
-		var defaultSiteOption int = 1
-		opt, err := strconv.Atoi(dsOptString)
-		if err != nil {
-			utils.SendErrorResponse(w, "invalid default site option")
-			return
-		}
-
-		defaultSiteOption = opt
-
-		dsVal, err := utils.PostPara(r, "defaultSiteVal")
-		if err != nil && (defaultSiteOption == 1 || defaultSiteOption == 2) {
-			//Reverse proxy or redirect, must require value to be set
-			utils.SendErrorResponse(w, "target not defined")
-			return
-		}
-
-		//Write the root options to file
-		rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
-			ProxyType:                dynamicproxy.ProxyType_Root,
-			RootOrMatchingDomain:     "/",
-			Domain:                   endpoint,
-			RequireTLS:               useTLS,
-			BypassGlobalTLS:          false,
-			SkipCertValidations:      false,
-			SkipWebSocketOriginCheck: true,
-
-			DefaultSiteOption: defaultSiteOption,
-			DefaultSiteValue:  dsVal,
-		}
-		preparedRootProxyRoute, err := dynamicProxyRouter.PrepareProxyRoute(&rootRoutingEndpoint)
-		if err != nil {
-			utils.SendErrorResponse(w, "unable to prepare root routing: "+err.Error())
-			return
-		}
-
-		dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
-		proxyEndpointCreated = &rootRoutingEndpoint
-	} else {
-		//Invalid eptype
-		utils.SendErrorResponse(w, "invalid endpoint type")
-		return
-	}
-
-	//Save the config to file
-	err = SaveReverseProxyConfig(proxyEndpointCreated)
-	if err != nil {
-		SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err)
-		return
-	}
-
-	//Update utm if exists
-	UpdateUptimeMonitorTargets()
-
-	utils.SendOK(w)
-}
-
-/*
-ReverseProxyHandleEditEndpoint handles proxy endpoint edit
-(host only, for root use Default Site page to edit)
-This endpoint do not handle basic auth credential update.
-The credential will be loaded from old config and reused
-*/
-func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
-	rootNameOrMatchingDomain, err := utils.PostPara(r, "rootname")
-	if err != nil {
-		utils.SendErrorResponse(w, "Target proxy rule not defined")
-		return
-	}
-
-	endpoint, err := utils.PostPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "endpoint not defined")
-		return
-	}
-
-	tls, _ := utils.PostPara(r, "tls")
-	if tls == "" {
-		tls = "false"
-	}
-
-	useTLS := (tls == "true")
-
-	stv, _ := utils.PostPara(r, "tlsval")
-	if stv == "" {
-		stv = "false"
-	}
-	skipTlsValidation := (stv == "true")
-
-	//Load bypass TLS option
-	bpgtls, _ := utils.PostPara(r, "bpgtls")
-	if bpgtls == "" {
-		bpgtls = "false"
-	}
-	bypassGlobalTLS := (bpgtls == "true")
-
-	// Basic Auth
-	rba, _ := utils.PostPara(r, "bauth")
-	if rba == "" {
-		rba = "false"
-	}
-
-	requireBasicAuth := (rba == "true")
-
-	// Bypass WebSocket Origin Check
-	strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
-	if strbpwsorg == "" {
-		strbpwsorg = "false"
-	}
-	bypassWebsocketOriginCheck := (strbpwsorg == "true")
-
-	//Load the previous basic auth credentials from current proxy rules
-	targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
-	if err != nil {
-		utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
-		return
-	}
-
-	//Generate a new proxyEndpoint from the new config
-	newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
-	newProxyEndpoint.Domain = endpoint
-	newProxyEndpoint.RequireTLS = useTLS
-	newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
-	newProxyEndpoint.SkipCertValidations = skipTlsValidation
-	newProxyEndpoint.RequireBasicAuth = requireBasicAuth
-	newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
-
-	//Prepare to replace the current routing rule
-	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-	targetProxyEntry.Remove()
-	dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
-
-	//Save it to file
-	SaveReverseProxyConfig(newProxyEndpoint)
-
-	//Update uptime monitor
-	UpdateUptimeMonitorTargets()
-
-	utils.SendOK(w)
-}
-
-func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
-	rootNameOrMatchingDomain, err := utils.PostPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid ep given")
-		return
-	}
-
-	//No need to check for type as root (/) can be set to default route
-	//and hence, you will not need alias
-
-	//Load the previous alias from current proxy rules
-	targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
-	if err != nil {
-		utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
-		return
-	}
-
-	newAliasJSON, err := utils.PostPara(r, "alias")
-	if err != nil {
-		//No new set of alias given
-		utils.SendErrorResponse(w, "new alias not given")
-		return
-	}
-
-	//Write new alias to runtime and file
-	newAlias := []string{}
-	err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
-	if err != nil {
-		SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
-		utils.SendErrorResponse(w, "Invalid alias list given")
-		return
-	}
-
-	//Set the current alias
-	newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
-	newProxyEndpoint.MatchingDomainAlias = newAlias
-
-	// Prepare to replace the current routing rule
-	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-	targetProxyEntry.Remove()
-	dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
-
-	// Save it to file
-	err = SaveReverseProxyConfig(newProxyEndpoint)
-	if err != nil {
-		utils.SendErrorResponse(w, "Alias update failed")
-		SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
-	}
-
-	utils.SendOK(w)
-}
-
-func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
-	ep, err := utils.GetPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid ep given")
-		return
-	}
-
-	//Remove the config from runtime
-	err = dynamicProxyRouter.RemoveProxyEndpointByRootname(ep)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-
-	//Remove the config from file
-	err = RemoveReverseProxyConfig(ep)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-
-	//Update utm if exists
-	if uptimeMonitor != nil {
-		uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter)
-		uptimeMonitor.CleanRecords()
-	}
-
-	//Update uptime monitor
-	UpdateUptimeMonitorTargets()
-
-	utils.SendOK(w)
-}
-
-/*
-Handle update request for basic auth credential
-Require paramter: ep (Endpoint) and pytype (proxy Type)
-if request with GET, the handler will return current credentials
-on this endpoint by its username
-
-if request is POST, the handler will write the results to proxy config
-*/
-func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
-	if r.Method == http.MethodGet {
-		ep, err := utils.GetPara(r, "ep")
-		if err != nil {
-			utils.SendErrorResponse(w, "Invalid ep given")
-			return
-		}
-
-		//Load the target proxy object from router
-		targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
-		if err != nil {
-			utils.SendErrorResponse(w, err.Error())
-			return
-		}
-
-		usernames := []string{}
-		for _, cred := range targetProxy.BasicAuthCredentials {
-			usernames = append(usernames, cred.Username)
-		}
-
-		js, _ := json.Marshal(usernames)
-		utils.SendJSONResponse(w, string(js))
-
-	} else if r.Method == http.MethodPost {
-		//Write to target
-		ep, err := utils.PostPara(r, "ep")
-		if err != nil {
-			utils.SendErrorResponse(w, "Invalid ep given")
-			return
-		}
-
-		creds, err := utils.PostPara(r, "creds")
-		if err != nil {
-			utils.SendErrorResponse(w, "Invalid ptype given")
-			return
-		}
-
-		//Load the target proxy object from router
-		targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
-		if err != nil {
-			utils.SendErrorResponse(w, err.Error())
-			return
-		}
-
-		//Try to marshal the content of creds into the suitable structure
-		newCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
-		err = json.Unmarshal([]byte(creds), &newCredentials)
-		if err != nil {
-			utils.SendErrorResponse(w, "Malformed credential data")
-			return
-		}
-
-		//Merge the credentials into the original config
-		//If a new username exists in old config with no pw given, keep the old pw hash
-		//If a new username is found with new password, hash it and push to credential slice
-		mergedCredentials := []*dynamicproxy.BasicAuthCredentials{}
-		for _, credential := range newCredentials {
-			if credential.Password == "" {
-				//Check if exists in the old credential files
-				keepUnchange := false
-				for _, oldCredEntry := range targetProxy.BasicAuthCredentials {
-					if oldCredEntry.Username == credential.Username {
-						//Exists! Reuse the old hash
-						mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
-							Username:     oldCredEntry.Username,
-							PasswordHash: oldCredEntry.PasswordHash,
-						})
-						keepUnchange = true
-					}
-				}
-
-				if !keepUnchange {
-					//This is a new username with no pw given
-					utils.SendErrorResponse(w, "Access password for "+credential.Username+" is empty!")
-					return
-				}
-			} else {
-				//This username have given password
-				mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
-					Username:     credential.Username,
-					PasswordHash: auth.Hash(credential.Password),
-				})
-			}
-		}
-
-		targetProxy.BasicAuthCredentials = mergedCredentials
-
-		//Save it to file
-		SaveReverseProxyConfig(targetProxy)
-
-		//Replace runtime configuration
-		targetProxy.UpdateToRuntime()
-		utils.SendOK(w)
-	} else {
-		http.Error(w, "invalid usage", http.StatusMethodNotAllowed)
-	}
-
-}
-
-// List, Update or Remove the exception paths for basic auth.
-func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
-
-	if r.Method != http.MethodGet {
-		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
-	}
-	ep, err := utils.GetPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid ep given")
-		return
-	}
-
-	//Load the target proxy object from router
-	targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-
-	//List all the exception paths for this proxy
-	results := targetProxy.BasicAuthExceptionRules
-	if results == nil {
-		//It is a config from a really old version of zoraxy. Overwrite it with empty array
-		results = []*dynamicproxy.BasicAuthExceptionRule{}
-	}
-	js, _ := json.Marshal(results)
-	utils.SendJSONResponse(w, string(js))
-	return
-}
-
-func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
-	ep, err := utils.PostPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid ep given")
-		return
-	}
-
-	matchingPrefix, err := utils.PostPara(r, "prefix")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid matching prefix given")
-		return
-	}
-
-	//Load the target proxy object from router
-	targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-
-	//Check if the prefix starts with /. If not, prepend it
-	if !strings.HasPrefix(matchingPrefix, "/") {
-		matchingPrefix = "/" + matchingPrefix
-	}
-
-	//Add a new exception rule if it is not already exists
-	alreadyExists := false
-	for _, thisExceptionRule := range targetProxy.BasicAuthExceptionRules {
-		if thisExceptionRule.PathPrefix == matchingPrefix {
-			alreadyExists = true
-			break
-		}
-	}
-	if alreadyExists {
-		utils.SendErrorResponse(w, "This matching path already exists")
-		return
-	}
-	targetProxy.BasicAuthExceptionRules = append(targetProxy.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
-		PathPrefix: strings.TrimSpace(matchingPrefix),
-	})
-
-	//Save configs to runtime and file
-	targetProxy.UpdateToRuntime()
-	SaveReverseProxyConfig(targetProxy)
-
-	utils.SendOK(w)
-}
-
-func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
-	// Delete a rule
-	ep, err := utils.PostPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid ep given")
-		return
-	}
-
-	matchingPrefix, err := utils.PostPara(r, "prefix")
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid matching prefix given")
-		return
-	}
-
-	// Load the target proxy object from router
-	targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
-	if err != nil {
-		utils.SendErrorResponse(w, err.Error())
-		return
-	}
-
-	newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{}
-	matchingExists := false
-	for _, thisExceptionalRule := range targetProxy.BasicAuthExceptionRules {
-		if thisExceptionalRule.PathPrefix != matchingPrefix {
-			newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
-		} else {
-			matchingExists = true
-		}
-	}
-
-	if !matchingExists {
-		utils.SendErrorResponse(w, "target matching rule not exists")
-		return
-	}
-
-	targetProxy.BasicAuthExceptionRules = newExceptionRuleList
-
-	// Save configs to runtime and file
-	targetProxy.UpdateToRuntime()
-	SaveReverseProxyConfig(targetProxy)
-
-	utils.SendOK(w)
-}
-
-// Report the current status of the reverse proxy server
-func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
-	js, _ := json.Marshal(dynamicProxyRouter)
-	utils.SendJSONResponse(w, string(js))
-}
-
-// Toggle a certain rule on and off
-func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
-	//No need to check for type as root cannot be turned off
-	ep, err := utils.PostPara(r, "ep")
-	if err != nil {
-		utils.SendErrorResponse(w, "invalid ep given")
-		return
-	}
-
-	targetProxyRule, err := dynamicProxyRouter.LoadProxy(ep)
-	if err != nil {
-		utils.SendErrorResponse(w, "invalid endpoint given")
-		return
-	}
-
-	enableStr, err := utils.PostPara(r, "enable")
-	if err != nil {
-		enableStr = "true"
-	}
-
-	//Flip the enable and disabled tag state
-	ruleDisabled := enableStr == "false"
-
-	targetProxyRule.Disabled = ruleDisabled
-	err = SaveReverseProxyConfig(targetProxyRule)
-	if err != nil {
-		utils.SendErrorResponse(w, "unable to save updated rule")
-		return
-	}
-	utils.SendOK(w)
-}
-
-func ReverseProxyListDetail(w http.ResponseWriter, r *http.Request) {
-	eptype, err := utils.PostPara(r, "type") //Support root and host
-	if err != nil {
-		utils.SendErrorResponse(w, "type not defined")
-		return
-	}
-
-	if eptype == "host" {
-		epname, err := utils.PostPara(r, "epname")
-		if err != nil {
-			utils.SendErrorResponse(w, "epname not defined")
-			return
-		}
-		endpointRaw, ok := dynamicProxyRouter.ProxyEndpoints.Load(epname)
-		if !ok {
-			utils.SendErrorResponse(w, "proxy rule not found")
-			return
-		}
-		targetEndpoint := dynamicproxy.CopyEndpoint(endpointRaw.(*dynamicproxy.ProxyEndpoint))
-		js, _ := json.Marshal(targetEndpoint)
-		utils.SendJSONResponse(w, string(js))
-	} else if eptype == "root" {
-		js, _ := json.Marshal(dynamicProxyRouter.Root)
-		utils.SendJSONResponse(w, string(js))
-	} else {
-		utils.SendErrorResponse(w, "Invalid type given")
-	}
-}
-
-func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
-	eptype, err := utils.PostPara(r, "type") //Support root and host
-	if err != nil {
-		utils.SendErrorResponse(w, "type not defined")
-		return
-	}
-
-	if eptype == "host" {
-		results := []*dynamicproxy.ProxyEndpoint{}
-		dynamicProxyRouter.ProxyEndpoints.Range(func(key, value interface{}) bool {
-			thisEndpoint := dynamicproxy.CopyEndpoint(value.(*dynamicproxy.ProxyEndpoint))
-
-			//Clear the auth passwords before showing to front-end
-			cleanedCredentials := []*dynamicproxy.BasicAuthCredentials{}
-			for _, user := range thisEndpoint.BasicAuthCredentials {
-				cleanedCredentials = append(cleanedCredentials, &dynamicproxy.BasicAuthCredentials{
-					Username:     user.Username,
-					PasswordHash: "",
-				})
-			}
-
-			thisEndpoint.BasicAuthCredentials = cleanedCredentials
-			results = append(results, thisEndpoint)
-			return true
-		})
-
-		sort.Slice(results, func(i, j int) bool {
-			return results[i].Domain < results[j].Domain
-		})
-
-		js, _ := json.Marshal(results)
-		utils.SendJSONResponse(w, string(js))
-	} else if eptype == "root" {
-		js, _ := json.Marshal(dynamicProxyRouter.Root)
-		utils.SendJSONResponse(w, string(js))
-	} else {
-		utils.SendErrorResponse(w, "Invalid type given")
-	}
-}
-
-// Handle port 80 incoming traffics
-func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
-	enabled, err := utils.GetPara(r, "enable")
-	if err != nil {
-		//Load the current status
-		currentEnabled := false
-		err = sysdb.Read("settings", "listenP80", &currentEnabled)
-		if err != nil {
-			utils.SendErrorResponse(w, err.Error())
-			return
-		}
-		js, _ := json.Marshal(currentEnabled)
-		utils.SendJSONResponse(w, string(js))
-	} else {
-		if enabled == "true" {
-			sysdb.Write("settings", "listenP80", true)
-			SystemWideLogger.Println("Enabling port 80 listener")
-			dynamicProxyRouter.UpdatePort80ListenerState(true)
-		} else if enabled == "false" {
-			sysdb.Write("settings", "listenP80", false)
-			SystemWideLogger.Println("Disabling port 80 listener")
-			dynamicProxyRouter.UpdatePort80ListenerState(false)
-		} else {
-			utils.SendErrorResponse(w, "invalid mode given: "+enabled)
-		}
-		utils.SendOK(w)
-	}
-}
-
-// Handle https redirect
-func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
-	useRedirect, err := utils.GetPara(r, "set")
-	if err != nil {
-		currentRedirectToHttps := false
-		//Load the current status
-		err = sysdb.Read("settings", "redirect", &currentRedirectToHttps)
-		if err != nil {
-			utils.SendErrorResponse(w, err.Error())
-			return
-		}
-		js, _ := json.Marshal(currentRedirectToHttps)
-		utils.SendJSONResponse(w, string(js))
-	} else {
-		if dynamicProxyRouter.Option.Port == 80 {
-			utils.SendErrorResponse(w, "This option is not available when listening on port 80")
-			return
-		}
-		if useRedirect == "true" {
-			sysdb.Write("settings", "redirect", true)
-			SystemWideLogger.Println("Updating force HTTPS redirection to true")
-			dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
-		} else if useRedirect == "false" {
-			sysdb.Write("settings", "redirect", false)
-			SystemWideLogger.Println("Updating force HTTPS redirection to false")
-			dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
-		}
-
-		utils.SendOK(w)
-	}
-}
-
-// Handle checking if the current user is accessing via the reverse proxied interface
-// Of the management interface.
-func HandleManagementProxyCheck(w http.ResponseWriter, r *http.Request) {
-	isProxied := dynamicProxyRouter.IsProxiedSubdomain(r)
-	js, _ := json.Marshal(isProxied)
-	utils.SendJSONResponse(w, string(js))
-}
-
-func HandleDevelopmentModeChange(w http.ResponseWriter, r *http.Request) {
-	enableDevelopmentModeStr, err := utils.GetPara(r, "enable")
-	if err != nil {
-		//Load the current development mode toggle state
-		js, _ := json.Marshal(dynamicProxyRouter.Option.NoCache)
-		utils.SendJSONResponse(w, string(js))
-	} else {
-		//Write changes to runtime
-		enableDevelopmentMode := false
-		if enableDevelopmentModeStr == "true" {
-			enableDevelopmentMode = true
-		}
-
-		//Write changes to runtime
-		dynamicProxyRouter.Option.NoCache = enableDevelopmentMode
-
-		//Write changes to database
-		sysdb.Write("settings", "devMode", enableDevelopmentMode)
-
-		utils.SendOK(w)
-	}
-
-}
-
-// Handle incoming port set. Change the current proxy incoming port
-func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
-	newIncomingPort, err := utils.PostPara(r, "incoming")
-	if err != nil {
-		utils.SendErrorResponse(w, "invalid incoming port given")
-		return
-	}
-
-	newIncomingPortInt, err := strconv.Atoi(newIncomingPort)
-	if err != nil {
-		utils.SendErrorResponse(w, "Invalid incoming port given")
-		return
-	}
-
-	//Check if it is identical as proxy root (recursion!)
-	if dynamicProxyRouter.Root == nil || dynamicProxyRouter.Root.Domain == "" {
-		//Check if proxy root is set before checking recursive listen
-		//Fixing issue #43
-		utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
-		return
-	}
-
-	proxyRoot := strings.TrimSuffix(dynamicProxyRouter.Root.Domain, "/")
-	if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
-		//Listening port is same as proxy root
-		//Not allow recursive settings
-		utils.SendErrorResponse(w, "Recursive listening port! Check your proxy root settings.")
-		return
-	}
-
-	//Stop and change the setting of the reverse proxy service
-	if dynamicProxyRouter.Running {
-		dynamicProxyRouter.StopProxyService()
-		dynamicProxyRouter.Option.Port = newIncomingPortInt
-		dynamicProxyRouter.StartProxyService()
-	} else {
-		//Only change setting but not starting the proxy service
-		dynamicProxyRouter.Option.Port = newIncomingPortInt
-	}
-
-	sysdb.Write("settings", "inbound", newIncomingPortInt)
-
-	utils.SendOK(w)
-}
-
-/* Handle Custom Header Rules */
-//List all the custom header defined in this proxy rule
-
-func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
-	epType, err := utils.PostPara(r, "type")
-	if err != nil {
-		utils.SendErrorResponse(w, "endpoint type not defined")
-		return
-	}
-
-	domain, err := utils.PostPara(r, "domain")
-	if err != nil {
-		utils.SendErrorResponse(w, "domain or matching rule not defined")
-		return
-	}
-
-	var targetProxyEndpoint *dynamicproxy.ProxyEndpoint
-	if epType == "root" {
-		targetProxyEndpoint = dynamicProxyRouter.Root
-	} else {
-		ep, err := dynamicProxyRouter.LoadProxy(domain)
-		if err != nil {
-			utils.SendErrorResponse(w, "target endpoint not exists")
-			return
-		}
-
-		targetProxyEndpoint = ep
-	}
-
-	//List all custom headers
-	customHeaderList := targetProxyEndpoint.UserDefinedHeaders
-	if customHeaderList == nil {
-		customHeaderList = []*dynamicproxy.UserDefinedHeader{}
-	}
-	js, _ := json.Marshal(customHeaderList)
-	utils.SendJSONResponse(w, string(js))
-
-}
-
-// Add a new header to the target endpoint
-func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
-	rewriteType, err := utils.PostPara(r, "type")
-	if err != nil {
-		utils.SendErrorResponse(w, "rewriteType not defined")
-		return
-	}
-
-	domain, err := utils.PostPara(r, "domain")
-	if err != nil {
-		utils.SendErrorResponse(w, "domain or matching rule not defined")
-		return
-	}
-
-	direction, err := utils.PostPara(r, "direction")
-	if err != nil {
-		utils.SendErrorResponse(w, "HTTP modifiy direction not set")
-		return
-	}
-
-	name, err := utils.PostPara(r, "name")
-	if err != nil {
-		utils.SendErrorResponse(w, "HTTP header name not set")
-		return
-	}
-
-	value, err := utils.PostPara(r, "value")
-	if err != nil && rewriteType == "add" {
-		utils.SendErrorResponse(w, "HTTP header value not set")
-		return
-	}
-
-	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
-	if err != nil {
-		utils.SendErrorResponse(w, "target endpoint not exists")
-		return
-	}
-
-	//Create a Custom Header Defination type
-	var rewriteDirection dynamicproxy.HeaderDirection
-	if direction == "toOrigin" {
-		rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToUpstream
-	} else if direction == "toClient" {
-		rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToDownstream
-	} else {
-		//Unknown direction
-		utils.SendErrorResponse(w, "header rewrite direction not supported")
-		return
-	}
-
-	isRemove := false
-	if rewriteType == "remove" {
-		isRemove = true
-	}
-	headerRewriteDefination := dynamicproxy.UserDefinedHeader{
-		Key:       name,
-		Value:     value,
-		Direction: rewriteDirection,
-		IsRemove:  isRemove,
-	}
-
-	//Create a new custom header object
-	err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefination)
-	if err != nil {
-		utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
-		return
-	}
-
-	//Save it (no need reload as header are not handled by dpcore)
-	err = SaveReverseProxyConfig(targetProxyEndpoint)
-	if err != nil {
-		utils.SendErrorResponse(w, "unable to save update")
-		return
-	}
-
-	utils.SendOK(w)
-}
-
-// Remove a header from the target endpoint
-func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
-	domain, err := utils.PostPara(r, "domain")
-	if err != nil {
-		utils.SendErrorResponse(w, "domain or matching rule not defined")
-		return
-	}
-
-	name, err := utils.PostPara(r, "name")
-	if err != nil {
-		utils.SendErrorResponse(w, "HTTP header name not set")
-		return
-	}
-
-	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
-	if err != nil {
-		utils.SendErrorResponse(w, "target endpoint not exists")
-		return
-	}
-
-	err = targetProxyEndpoint.RemoveUserDefinedHeader(name)
-	if err != nil {
-		utils.SendErrorResponse(w, "unable to remove header rewrite rule: "+err.Error())
-		return
-	}
-
-	err = SaveReverseProxyConfig(targetProxyEndpoint)
-	if err != nil {
-		utils.SendErrorResponse(w, "unable to save update")
-		return
-	}
-
-	utils.SendOK(w)
-
-}
+package main
+
+import (
+	"encoding/json"
+	"net/http"
+	"path/filepath"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"imuslab.com/zoraxy/mod/auth"
+	"imuslab.com/zoraxy/mod/dynamicproxy"
+	"imuslab.com/zoraxy/mod/uptime"
+	"imuslab.com/zoraxy/mod/utils"
+)
+
+var (
+	dynamicProxyRouter *dynamicproxy.Router
+)
+
+// Add user customizable reverse proxy
+func ReverseProxtInit() {
+	/*
+		Load Reverse Proxy Global Settings
+	*/
+	inboundPort := 80
+	if sysdb.KeyExists("settings", "inbound") {
+		sysdb.Read("settings", "inbound", &inboundPort)
+		SystemWideLogger.Println("Serving inbound port ", inboundPort)
+	} else {
+		SystemWideLogger.Println("Inbound port not set. Using default (80)")
+	}
+
+	useTls := false
+	sysdb.Read("settings", "usetls", &useTls)
+	if useTls {
+		SystemWideLogger.Println("TLS mode enabled. Serving proxxy request with TLS")
+	} else {
+		SystemWideLogger.Println("TLS mode disabled. Serving proxy request with plain http")
+	}
+
+	forceLatestTLSVersion := false
+	sysdb.Read("settings", "forceLatestTLS", &forceLatestTLSVersion)
+	if forceLatestTLSVersion {
+		SystemWideLogger.Println("Force latest TLS mode enabled. Minimum TLS LS version is set to v1.2")
+	} else {
+		SystemWideLogger.Println("Force latest TLS mode disabled. Minimum TLS version is set to v1.0")
+	}
+
+	developmentMode := false
+	sysdb.Read("settings", "devMode", &developmentMode)
+	if useTls {
+		SystemWideLogger.Println("Development mode enabled. Using no-store Cache Control policy")
+	} else {
+		SystemWideLogger.Println("Development mode disabled. Proxying with default Cache Control policy")
+	}
+
+	listenOnPort80 := false
+	sysdb.Read("settings", "listenP80", &listenOnPort80)
+	if listenOnPort80 {
+		SystemWideLogger.Println("Port 80 listener enabled")
+	} else {
+		SystemWideLogger.Println("Port 80 listener disabled")
+	}
+
+	forceHttpsRedirect := false
+	sysdb.Read("settings", "redirect", &forceHttpsRedirect)
+	if forceHttpsRedirect {
+		SystemWideLogger.Println("Force HTTPS mode enabled")
+		//Port 80 listener must be enabled to perform http -> https redirect
+		listenOnPort80 = true
+	} else {
+		SystemWideLogger.Println("Force HTTPS mode disabled")
+	}
+
+	/*
+		Create a new proxy object
+		The DynamicProxy is the parent of all reverse proxy handlers,
+		use for managemening and provide functions to access proxy handlers
+	*/
+
+	dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
+		HostUUID:           nodeUUID,
+		HostVersion:        version,
+		Port:               inboundPort,
+		UseTls:             useTls,
+		ForceTLSLatest:     forceLatestTLSVersion,
+		NoCache:            developmentMode,
+		ListenOnPort80:     listenOnPort80,
+		ForceHttpsRedirect: forceHttpsRedirect,
+		TlsManager:         tlsCertManager,
+		RedirectRuleTable:  redirectTable,
+		GeodbStore:         geodbStore,
+		StatisticCollector: statisticCollector,
+		WebDirectory:       *staticWebServerRoot,
+		AccessController:   accessController,
+	})
+	if err != nil {
+		SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
+		return
+	}
+
+	dynamicProxyRouter = dprouter
+
+	/*
+
+		Load all conf from files
+
+	*/
+	confs, _ := filepath.Glob("./conf/proxy/*.config")
+	for _, conf := range confs {
+		err := LoadReverseProxyConfig(conf)
+		if err != nil {
+			SystemWideLogger.PrintAndLog("Proxy", "Failed to load config file: "+filepath.Base(conf), err)
+			return
+		}
+	}
+
+	if dynamicProxyRouter.Root == nil {
+		//Root config not set (new deployment?), use internal static web server as root
+		defaultRootRouter, err := GetDefaultRootConfig()
+		if err != nil {
+			SystemWideLogger.PrintAndLog("Proxy", "Failed to generate default root routing", err)
+			return
+		}
+		dynamicProxyRouter.SetProxyRouteAsRoot(defaultRootRouter)
+	}
+
+	//Start Service
+	//Not sure why but delay must be added if you have another
+	//reverse proxy server in front of this service
+	time.Sleep(300 * time.Millisecond)
+	dynamicProxyRouter.StartProxyService()
+	SystemWideLogger.Println("Dynamic Reverse Proxy service started")
+
+	//Add all proxy services to uptime monitor
+	//Create a uptime monitor service
+	go func() {
+		//This must be done in go routine to prevent blocking on system startup
+		uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{
+			Targets:         GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
+			Interval:        300, //5 minutes
+			MaxRecordsStore: 288, //1 day
+		})
+		SystemWideLogger.Println("Uptime Monitor background service started")
+	}()
+
+	// Init Rate Limit
+	go func() {
+		dynamicproxy.InitRateLimit()
+	}()
+}
+
+func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
+	enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
+	if enable == "true" {
+		err := dynamicProxyRouter.StartProxyService()
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+	} else {
+		//Check if it is loopback
+		if dynamicProxyRouter.IsProxiedSubdomain(r) {
+			//Loopback routing. Turning it off will make the user lost control
+			//of the whole system. Do not allow shutdown
+			utils.SendErrorResponse(w, "Unable to shutdown in loopback rp mode. Remove proxy rules for management interface and retry.")
+			return
+		}
+
+		err := dynamicProxyRouter.StopProxyService()
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+	}
+
+	utils.SendOK(w)
+}
+
+func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
+	eptype, err := utils.PostPara(r, "type") //Support root and host
+	if err != nil {
+		utils.SendErrorResponse(w, "type not defined")
+		return
+	}
+
+	endpoint, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "endpoint not defined")
+		return
+	}
+
+	tls, _ := utils.PostPara(r, "tls")
+	if tls == "" {
+		tls = "false"
+	}
+
+	useTLS := (tls == "true")
+
+	//Bypass global TLS value / allow direct access from port 80?
+	bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
+	if bypassGlobalTLS == "" {
+		bypassGlobalTLS = "false"
+	}
+
+	useBypassGlobalTLS := bypassGlobalTLS == "true"
+
+	//Enable TLS validation?
+	stv, _ := utils.PostPara(r, "tlsval")
+	if stv == "" {
+		stv = "false"
+	}
+
+	skipTlsValidation := (stv == "true")
+
+	//Get access rule ID
+	accessRuleID, _ := utils.PostPara(r, "access")
+	if accessRuleID == "" {
+		accessRuleID = "default"
+	}
+	if !accessController.AccessRuleExists(accessRuleID) {
+		utils.SendErrorResponse(w, "invalid access rule ID selected")
+		return
+	}
+
+	//Require basic auth?
+	rba, _ := utils.PostPara(r, "bauth")
+	if rba == "" {
+		rba = "false"
+	}
+
+	requireBasicAuth := (rba == "true")
+
+	// Require Rate Limiting?
+	rl, _ := utils.PostPara(r, "rate")
+	if rl == "" {
+		rl = "false"
+	}
+	requireRateLimit := (rl == "true")
+	rlnum, _ := utils.PostPara(r, "ratenum")
+	if rlnum == "" {
+		rlnum = "0"
+	}
+	proxyRateLimit, err := strconv.ParseInt(rlnum, 10, 64)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid rate limit number")
+		return
+	}
+	if proxyRateLimit <= 0 {
+		utils.SendErrorResponse(w, "rate limit number must be greater than 0")
+		return
+	}
+
+	// Bypass WebSocket Origin Check
+	strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
+	if strbpwsorg == "" {
+		strbpwsorg = "false"
+	}
+	bypassWebsocketOriginCheck := (strbpwsorg == "true")
+
+	//Prase the basic auth to correct structure
+	cred, _ := utils.PostPara(r, "cred")
+	basicAuthCredentials := []*dynamicproxy.BasicAuthCredentials{}
+	if requireBasicAuth {
+		preProcessCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
+		err = json.Unmarshal([]byte(cred), &preProcessCredentials)
+		if err != nil {
+			utils.SendErrorResponse(w, "invalid user credentials")
+			return
+		}
+
+		//Check if there are empty password credentials
+		for _, credObj := range preProcessCredentials {
+			if strings.TrimSpace(credObj.Password) == "" {
+				utils.SendErrorResponse(w, credObj.Username+" has empty password")
+				return
+			}
+		}
+
+		//Convert and hash the passwords
+		for _, credObj := range preProcessCredentials {
+			basicAuthCredentials = append(basicAuthCredentials, &dynamicproxy.BasicAuthCredentials{
+				Username:     credObj.Username,
+				PasswordHash: auth.Hash(credObj.Password),
+			})
+		}
+	}
+
+	var proxyEndpointCreated *dynamicproxy.ProxyEndpoint
+	if eptype == "host" {
+		rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
+		if err != nil {
+			utils.SendErrorResponse(w, "hostname not defined")
+			return
+		}
+		rootOrMatchingDomain = strings.TrimSpace(rootOrMatchingDomain)
+
+		//Check if it contains ",", if yes, split the remainings as alias
+		aliasHostnames := []string{}
+		if strings.Contains(rootOrMatchingDomain, ",") {
+			matchingDomains := strings.Split(rootOrMatchingDomain, ",")
+			if len(matchingDomains) > 1 {
+				rootOrMatchingDomain = matchingDomains[0]
+				for _, aliasHostname := range matchingDomains[1:] {
+					//Filter out any space
+					aliasHostnames = append(aliasHostnames, strings.TrimSpace(aliasHostname))
+				}
+			}
+		}
+
+		//Generate a proxy endpoint object
+		thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
+			//I/O
+			ProxyType:            dynamicproxy.ProxyType_Host,
+			RootOrMatchingDomain: rootOrMatchingDomain,
+			MatchingDomainAlias:  aliasHostnames,
+			Domain:               endpoint,
+			//TLS
+			RequireTLS:               useTLS,
+			BypassGlobalTLS:          useBypassGlobalTLS,
+			SkipCertValidations:      skipTlsValidation,
+			SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
+			AccessFilterUUID:         accessRuleID,
+			//VDir
+			VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
+			//Custom headers
+			UserDefinedHeaders: []*dynamicproxy.UserDefinedHeader{},
+			//Auth
+			RequireBasicAuth:        requireBasicAuth,
+			BasicAuthCredentials:    basicAuthCredentials,
+			BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
+			DefaultSiteOption:       0,
+			DefaultSiteValue:        "",
+			// Rate Limit
+			RequireRateLimit: requireRateLimit,
+			RateLimit:        proxyRateLimit,
+		}
+
+		preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)
+		if err != nil {
+			utils.SendErrorResponse(w, "unable to prepare proxy route to target endpoint: "+err.Error())
+			return
+		}
+
+		dynamicProxyRouter.AddProxyRouteToRuntime(preparedEndpoint)
+		proxyEndpointCreated = &thisProxyEndpoint
+	} else if eptype == "root" {
+		//Get the default site options and target
+		dsOptString, err := utils.PostPara(r, "defaultSiteOpt")
+		if err != nil {
+			utils.SendErrorResponse(w, "default site action not defined")
+			return
+		}
+
+		var defaultSiteOption int = 1
+		opt, err := strconv.Atoi(dsOptString)
+		if err != nil {
+			utils.SendErrorResponse(w, "invalid default site option")
+			return
+		}
+
+		defaultSiteOption = opt
+
+		dsVal, err := utils.PostPara(r, "defaultSiteVal")
+		if err != nil && (defaultSiteOption == 1 || defaultSiteOption == 2) {
+			//Reverse proxy or redirect, must require value to be set
+			utils.SendErrorResponse(w, "target not defined")
+			return
+		}
+
+		//Write the root options to file
+		rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
+			ProxyType:                dynamicproxy.ProxyType_Root,
+			RootOrMatchingDomain:     "/",
+			Domain:                   endpoint,
+			RequireTLS:               useTLS,
+			BypassGlobalTLS:          false,
+			SkipCertValidations:      false,
+			SkipWebSocketOriginCheck: true,
+
+			DefaultSiteOption: defaultSiteOption,
+			DefaultSiteValue:  dsVal,
+		}
+		preparedRootProxyRoute, err := dynamicProxyRouter.PrepareProxyRoute(&rootRoutingEndpoint)
+		if err != nil {
+			utils.SendErrorResponse(w, "unable to prepare root routing: "+err.Error())
+			return
+		}
+
+		dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
+		proxyEndpointCreated = &rootRoutingEndpoint
+	} else {
+		//Invalid eptype
+		utils.SendErrorResponse(w, "invalid endpoint type")
+		return
+	}
+
+	//Save the config to file
+	err = SaveReverseProxyConfig(proxyEndpointCreated)
+	if err != nil {
+		SystemWideLogger.PrintAndLog("Proxy", "Unable to save new proxy rule to file", err)
+		return
+	}
+
+	//Update utm if exists
+	UpdateUptimeMonitorTargets()
+
+	utils.SendOK(w)
+}
+
+/*
+ReverseProxyHandleEditEndpoint handles proxy endpoint edit
+(host only, for root use Default Site page to edit)
+This endpoint do not handle basic auth credential update.
+The credential will be loaded from old config and reused
+*/
+func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
+	rootNameOrMatchingDomain, err := utils.PostPara(r, "rootname")
+	if err != nil {
+		utils.SendErrorResponse(w, "Target proxy rule not defined")
+		return
+	}
+
+	endpoint, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "endpoint not defined")
+		return
+	}
+
+	tls, _ := utils.PostPara(r, "tls")
+	if tls == "" {
+		tls = "false"
+	}
+
+	useTLS := (tls == "true")
+
+	stv, _ := utils.PostPara(r, "tlsval")
+	if stv == "" {
+		stv = "false"
+	}
+	skipTlsValidation := (stv == "true")
+
+	//Load bypass TLS option
+	bpgtls, _ := utils.PostPara(r, "bpgtls")
+	if bpgtls == "" {
+		bpgtls = "false"
+	}
+	bypassGlobalTLS := (bpgtls == "true")
+
+	// Basic Auth
+	rba, _ := utils.PostPara(r, "bauth")
+	if rba == "" {
+		rba = "false"
+	}
+
+	requireBasicAuth := (rba == "true")
+
+	// Rate Limiting?
+	rl, _ := utils.PostPara(r, "rate")
+	if rl == "" {
+		rl = "false"
+	}
+	requireRateLimit := (rl == "true")
+	rlnum, _ := utils.PostPara(r, "ratenum")
+	if rlnum == "" {
+		rlnum = "0"
+	}
+	proxyRateLimit, err := strconv.ParseInt(rlnum, 10, 64)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid rate limit number")
+		return
+	}
+	if proxyRateLimit <= 0 {
+		utils.SendErrorResponse(w, "rate limit number must be greater than 0")
+		return
+	}
+
+	// Bypass WebSocket Origin Check
+	strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
+	if strbpwsorg == "" {
+		strbpwsorg = "false"
+	}
+	bypassWebsocketOriginCheck := (strbpwsorg == "true")
+
+	//Load the previous basic auth credentials from current proxy rules
+	targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
+	if err != nil {
+		utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
+		return
+	}
+
+	//Generate a new proxyEndpoint from the new config
+	newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
+	newProxyEndpoint.Domain = endpoint
+	newProxyEndpoint.RequireTLS = useTLS
+	newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
+	newProxyEndpoint.SkipCertValidations = skipTlsValidation
+	newProxyEndpoint.RequireBasicAuth = requireBasicAuth
+	newProxyEndpoint.RequireRateLimit = requireRateLimit
+	newProxyEndpoint.RateLimit = proxyRateLimit
+	newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
+
+	//Prepare to replace the current routing rule
+	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+	targetProxyEntry.Remove()
+	dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
+
+	//Save it to file
+	SaveReverseProxyConfig(newProxyEndpoint)
+
+	//Update uptime monitor
+	UpdateUptimeMonitorTargets()
+
+	utils.SendOK(w)
+}
+
+func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
+	rootNameOrMatchingDomain, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid ep given")
+		return
+	}
+
+	//No need to check for type as root (/) can be set to default route
+	//and hence, you will not need alias
+
+	//Load the previous alias from current proxy rules
+	targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
+	if err != nil {
+		utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
+		return
+	}
+
+	newAliasJSON, err := utils.PostPara(r, "alias")
+	if err != nil {
+		//No new set of alias given
+		utils.SendErrorResponse(w, "new alias not given")
+		return
+	}
+
+	//Write new alias to runtime and file
+	newAlias := []string{}
+	err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
+	if err != nil {
+		SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
+		utils.SendErrorResponse(w, "Invalid alias list given")
+		return
+	}
+
+	//Set the current alias
+	newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
+	newProxyEndpoint.MatchingDomainAlias = newAlias
+
+	// Prepare to replace the current routing rule
+	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+	targetProxyEntry.Remove()
+	dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
+
+	// Save it to file
+	err = SaveReverseProxyConfig(newProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, "Alias update failed")
+		SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
+	}
+
+	utils.SendOK(w)
+}
+
+func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
+	ep, err := utils.GetPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid ep given")
+		return
+	}
+
+	//Remove the config from runtime
+	err = dynamicProxyRouter.RemoveProxyEndpointByRootname(ep)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Remove the config from file
+	err = RemoveReverseProxyConfig(ep)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Update utm if exists
+	if uptimeMonitor != nil {
+		uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter)
+		uptimeMonitor.CleanRecords()
+	}
+
+	//Update uptime monitor
+	UpdateUptimeMonitorTargets()
+
+	utils.SendOK(w)
+}
+
+/*
+Handle update request for basic auth credential
+Require paramter: ep (Endpoint) and pytype (proxy Type)
+if request with GET, the handler will return current credentials
+on this endpoint by its username
+
+if request is POST, the handler will write the results to proxy config
+*/
+func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
+	if r.Method == http.MethodGet {
+		ep, err := utils.GetPara(r, "ep")
+		if err != nil {
+			utils.SendErrorResponse(w, "Invalid ep given")
+			return
+		}
+
+		//Load the target proxy object from router
+		targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		usernames := []string{}
+		for _, cred := range targetProxy.BasicAuthCredentials {
+			usernames = append(usernames, cred.Username)
+		}
+
+		js, _ := json.Marshal(usernames)
+		utils.SendJSONResponse(w, string(js))
+
+	} else if r.Method == http.MethodPost {
+		//Write to target
+		ep, err := utils.PostPara(r, "ep")
+		if err != nil {
+			utils.SendErrorResponse(w, "Invalid ep given")
+			return
+		}
+
+		creds, err := utils.PostPara(r, "creds")
+		if err != nil {
+			utils.SendErrorResponse(w, "Invalid ptype given")
+			return
+		}
+
+		//Load the target proxy object from router
+		targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+
+		//Try to marshal the content of creds into the suitable structure
+		newCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
+		err = json.Unmarshal([]byte(creds), &newCredentials)
+		if err != nil {
+			utils.SendErrorResponse(w, "Malformed credential data")
+			return
+		}
+
+		//Merge the credentials into the original config
+		//If a new username exists in old config with no pw given, keep the old pw hash
+		//If a new username is found with new password, hash it and push to credential slice
+		mergedCredentials := []*dynamicproxy.BasicAuthCredentials{}
+		for _, credential := range newCredentials {
+			if credential.Password == "" {
+				//Check if exists in the old credential files
+				keepUnchange := false
+				for _, oldCredEntry := range targetProxy.BasicAuthCredentials {
+					if oldCredEntry.Username == credential.Username {
+						//Exists! Reuse the old hash
+						mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
+							Username:     oldCredEntry.Username,
+							PasswordHash: oldCredEntry.PasswordHash,
+						})
+						keepUnchange = true
+					}
+				}
+
+				if !keepUnchange {
+					//This is a new username with no pw given
+					utils.SendErrorResponse(w, "Access password for "+credential.Username+" is empty!")
+					return
+				}
+			} else {
+				//This username have given password
+				mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
+					Username:     credential.Username,
+					PasswordHash: auth.Hash(credential.Password),
+				})
+			}
+		}
+
+		targetProxy.BasicAuthCredentials = mergedCredentials
+
+		//Save it to file
+		SaveReverseProxyConfig(targetProxy)
+
+		//Replace runtime configuration
+		targetProxy.UpdateToRuntime()
+		utils.SendOK(w)
+	} else {
+		http.Error(w, "invalid usage", http.StatusMethodNotAllowed)
+	}
+
+}
+
+// List, Update or Remove the exception paths for basic auth.
+func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
+
+	if r.Method != http.MethodGet {
+		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
+	}
+	ep, err := utils.GetPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid ep given")
+		return
+	}
+
+	//Load the target proxy object from router
+	targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//List all the exception paths for this proxy
+	results := targetProxy.BasicAuthExceptionRules
+	if results == nil {
+		//It is a config from a really old version of zoraxy. Overwrite it with empty array
+		results = []*dynamicproxy.BasicAuthExceptionRule{}
+	}
+	js, _ := json.Marshal(results)
+	utils.SendJSONResponse(w, string(js))
+	return
+}
+
+func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
+	ep, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid ep given")
+		return
+	}
+
+	matchingPrefix, err := utils.PostPara(r, "prefix")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid matching prefix given")
+		return
+	}
+
+	//Load the target proxy object from router
+	targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Check if the prefix starts with /. If not, prepend it
+	if !strings.HasPrefix(matchingPrefix, "/") {
+		matchingPrefix = "/" + matchingPrefix
+	}
+
+	//Add a new exception rule if it is not already exists
+	alreadyExists := false
+	for _, thisExceptionRule := range targetProxy.BasicAuthExceptionRules {
+		if thisExceptionRule.PathPrefix == matchingPrefix {
+			alreadyExists = true
+			break
+		}
+	}
+	if alreadyExists {
+		utils.SendErrorResponse(w, "This matching path already exists")
+		return
+	}
+	targetProxy.BasicAuthExceptionRules = append(targetProxy.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
+		PathPrefix: strings.TrimSpace(matchingPrefix),
+	})
+
+	//Save configs to runtime and file
+	targetProxy.UpdateToRuntime()
+	SaveReverseProxyConfig(targetProxy)
+
+	utils.SendOK(w)
+}
+
+func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
+	// Delete a rule
+	ep, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid ep given")
+		return
+	}
+
+	matchingPrefix, err := utils.PostPara(r, "prefix")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid matching prefix given")
+		return
+	}
+
+	// Load the target proxy object from router
+	targetProxy, err := dynamicProxyRouter.LoadProxy(ep)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{}
+	matchingExists := false
+	for _, thisExceptionalRule := range targetProxy.BasicAuthExceptionRules {
+		if thisExceptionalRule.PathPrefix != matchingPrefix {
+			newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
+		} else {
+			matchingExists = true
+		}
+	}
+
+	if !matchingExists {
+		utils.SendErrorResponse(w, "target matching rule not exists")
+		return
+	}
+
+	targetProxy.BasicAuthExceptionRules = newExceptionRuleList
+
+	// Save configs to runtime and file
+	targetProxy.UpdateToRuntime()
+	SaveReverseProxyConfig(targetProxy)
+
+	utils.SendOK(w)
+}
+
+// Report the current status of the reverse proxy server
+func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
+	js, _ := json.Marshal(dynamicProxyRouter)
+	utils.SendJSONResponse(w, string(js))
+}
+
+// Toggle a certain rule on and off
+func ReverseProxyToggleRuleSet(w http.ResponseWriter, r *http.Request) {
+	//No need to check for type as root cannot be turned off
+	ep, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid ep given")
+		return
+	}
+
+	targetProxyRule, err := dynamicProxyRouter.LoadProxy(ep)
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid endpoint given")
+		return
+	}
+
+	enableStr, err := utils.PostPara(r, "enable")
+	if err != nil {
+		enableStr = "true"
+	}
+
+	//Flip the enable and disabled tag state
+	ruleDisabled := enableStr == "false"
+
+	targetProxyRule.Disabled = ruleDisabled
+	err = SaveReverseProxyConfig(targetProxyRule)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to save updated rule")
+		return
+	}
+	utils.SendOK(w)
+}
+
+func ReverseProxyListDetail(w http.ResponseWriter, r *http.Request) {
+	eptype, err := utils.PostPara(r, "type") //Support root and host
+	if err != nil {
+		utils.SendErrorResponse(w, "type not defined")
+		return
+	}
+
+	if eptype == "host" {
+		epname, err := utils.PostPara(r, "epname")
+		if err != nil {
+			utils.SendErrorResponse(w, "epname not defined")
+			return
+		}
+		endpointRaw, ok := dynamicProxyRouter.ProxyEndpoints.Load(epname)
+		if !ok {
+			utils.SendErrorResponse(w, "proxy rule not found")
+			return
+		}
+		targetEndpoint := dynamicproxy.CopyEndpoint(endpointRaw.(*dynamicproxy.ProxyEndpoint))
+		js, _ := json.Marshal(targetEndpoint)
+		utils.SendJSONResponse(w, string(js))
+	} else if eptype == "root" {
+		js, _ := json.Marshal(dynamicProxyRouter.Root)
+		utils.SendJSONResponse(w, string(js))
+	} else {
+		utils.SendErrorResponse(w, "Invalid type given")
+	}
+}
+
+func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
+	eptype, err := utils.PostPara(r, "type") //Support root and host
+	if err != nil {
+		utils.SendErrorResponse(w, "type not defined")
+		return
+	}
+
+	if eptype == "host" {
+		results := []*dynamicproxy.ProxyEndpoint{}
+		dynamicProxyRouter.ProxyEndpoints.Range(func(key, value interface{}) bool {
+			thisEndpoint := dynamicproxy.CopyEndpoint(value.(*dynamicproxy.ProxyEndpoint))
+
+			//Clear the auth passwords before showing to front-end
+			cleanedCredentials := []*dynamicproxy.BasicAuthCredentials{}
+			for _, user := range thisEndpoint.BasicAuthCredentials {
+				cleanedCredentials = append(cleanedCredentials, &dynamicproxy.BasicAuthCredentials{
+					Username:     user.Username,
+					PasswordHash: "",
+				})
+			}
+
+			thisEndpoint.BasicAuthCredentials = cleanedCredentials
+			results = append(results, thisEndpoint)
+			return true
+		})
+
+		sort.Slice(results, func(i, j int) bool {
+			return results[i].Domain < results[j].Domain
+		})
+
+		js, _ := json.Marshal(results)
+		utils.SendJSONResponse(w, string(js))
+	} else if eptype == "root" {
+		js, _ := json.Marshal(dynamicProxyRouter.Root)
+		utils.SendJSONResponse(w, string(js))
+	} else {
+		utils.SendErrorResponse(w, "Invalid type given")
+	}
+}
+
+// Handle port 80 incoming traffics
+func HandleUpdatePort80Listener(w http.ResponseWriter, r *http.Request) {
+	enabled, err := utils.GetPara(r, "enable")
+	if err != nil {
+		//Load the current status
+		currentEnabled := false
+		err = sysdb.Read("settings", "listenP80", &currentEnabled)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+		js, _ := json.Marshal(currentEnabled)
+		utils.SendJSONResponse(w, string(js))
+	} else {
+		if enabled == "true" {
+			sysdb.Write("settings", "listenP80", true)
+			SystemWideLogger.Println("Enabling port 80 listener")
+			dynamicProxyRouter.UpdatePort80ListenerState(true)
+		} else if enabled == "false" {
+			sysdb.Write("settings", "listenP80", false)
+			SystemWideLogger.Println("Disabling port 80 listener")
+			dynamicProxyRouter.UpdatePort80ListenerState(false)
+		} else {
+			utils.SendErrorResponse(w, "invalid mode given: "+enabled)
+		}
+		utils.SendOK(w)
+	}
+}
+
+// Handle https redirect
+func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
+	useRedirect, err := utils.GetPara(r, "set")
+	if err != nil {
+		currentRedirectToHttps := false
+		//Load the current status
+		err = sysdb.Read("settings", "redirect", &currentRedirectToHttps)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+		js, _ := json.Marshal(currentRedirectToHttps)
+		utils.SendJSONResponse(w, string(js))
+	} else {
+		if dynamicProxyRouter.Option.Port == 80 {
+			utils.SendErrorResponse(w, "This option is not available when listening on port 80")
+			return
+		}
+		if useRedirect == "true" {
+			sysdb.Write("settings", "redirect", true)
+			SystemWideLogger.Println("Updating force HTTPS redirection to true")
+			dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
+		} else if useRedirect == "false" {
+			sysdb.Write("settings", "redirect", false)
+			SystemWideLogger.Println("Updating force HTTPS redirection to false")
+			dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
+		}
+
+		utils.SendOK(w)
+	}
+}
+
+// Handle checking if the current user is accessing via the reverse proxied interface
+// Of the management interface.
+func HandleManagementProxyCheck(w http.ResponseWriter, r *http.Request) {
+	isProxied := dynamicProxyRouter.IsProxiedSubdomain(r)
+	js, _ := json.Marshal(isProxied)
+	utils.SendJSONResponse(w, string(js))
+}
+
+func HandleDevelopmentModeChange(w http.ResponseWriter, r *http.Request) {
+	enableDevelopmentModeStr, err := utils.GetPara(r, "enable")
+	if err != nil {
+		//Load the current development mode toggle state
+		js, _ := json.Marshal(dynamicProxyRouter.Option.NoCache)
+		utils.SendJSONResponse(w, string(js))
+	} else {
+		//Write changes to runtime
+		enableDevelopmentMode := false
+		if enableDevelopmentModeStr == "true" {
+			enableDevelopmentMode = true
+		}
+
+		//Write changes to runtime
+		dynamicProxyRouter.Option.NoCache = enableDevelopmentMode
+
+		//Write changes to database
+		sysdb.Write("settings", "devMode", enableDevelopmentMode)
+
+		utils.SendOK(w)
+	}
+
+}
+
+// Handle incoming port set. Change the current proxy incoming port
+func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
+	newIncomingPort, err := utils.PostPara(r, "incoming")
+	if err != nil {
+		utils.SendErrorResponse(w, "invalid incoming port given")
+		return
+	}
+
+	newIncomingPortInt, err := strconv.Atoi(newIncomingPort)
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid incoming port given")
+		return
+	}
+
+	//Check if it is identical as proxy root (recursion!)
+	if dynamicProxyRouter.Root == nil || dynamicProxyRouter.Root.Domain == "" {
+		//Check if proxy root is set before checking recursive listen
+		//Fixing issue #43
+		utils.SendErrorResponse(w, "Set Proxy Root before changing inbound port")
+		return
+	}
+
+	proxyRoot := strings.TrimSuffix(dynamicProxyRouter.Root.Domain, "/")
+	if strings.EqualFold(proxyRoot, "localhost:"+strconv.Itoa(newIncomingPortInt)) || strings.EqualFold(proxyRoot, "127.0.0.1:"+strconv.Itoa(newIncomingPortInt)) {
+		//Listening port is same as proxy root
+		//Not allow recursive settings
+		utils.SendErrorResponse(w, "Recursive listening port! Check your proxy root settings.")
+		return
+	}
+
+	//Stop and change the setting of the reverse proxy service
+	if dynamicProxyRouter.Running {
+		dynamicProxyRouter.StopProxyService()
+		dynamicProxyRouter.Option.Port = newIncomingPortInt
+		dynamicProxyRouter.StartProxyService()
+	} else {
+		//Only change setting but not starting the proxy service
+		dynamicProxyRouter.Option.Port = newIncomingPortInt
+	}
+
+	sysdb.Write("settings", "inbound", newIncomingPortInt)
+
+	utils.SendOK(w)
+}
+
+/* Handle Custom Header Rules */
+//List all the custom header defined in this proxy rule
+
+func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
+	epType, err := utils.PostPara(r, "type")
+	if err != nil {
+		utils.SendErrorResponse(w, "endpoint type not defined")
+		return
+	}
+
+	domain, err := utils.PostPara(r, "domain")
+	if err != nil {
+		utils.SendErrorResponse(w, "domain or matching rule not defined")
+		return
+	}
+
+	var targetProxyEndpoint *dynamicproxy.ProxyEndpoint
+	if epType == "root" {
+		targetProxyEndpoint = dynamicProxyRouter.Root
+	} else {
+		ep, err := dynamicProxyRouter.LoadProxy(domain)
+		if err != nil {
+			utils.SendErrorResponse(w, "target endpoint not exists")
+			return
+		}
+
+		targetProxyEndpoint = ep
+	}
+
+	//List all custom headers
+	customHeaderList := targetProxyEndpoint.UserDefinedHeaders
+	if customHeaderList == nil {
+		customHeaderList = []*dynamicproxy.UserDefinedHeader{}
+	}
+	js, _ := json.Marshal(customHeaderList)
+	utils.SendJSONResponse(w, string(js))
+
+}
+
+// Add a new header to the target endpoint
+func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
+	rewriteType, err := utils.PostPara(r, "type")
+	if err != nil {
+		utils.SendErrorResponse(w, "rewriteType not defined")
+		return
+	}
+
+	domain, err := utils.PostPara(r, "domain")
+	if err != nil {
+		utils.SendErrorResponse(w, "domain or matching rule not defined")
+		return
+	}
+
+	direction, err := utils.PostPara(r, "direction")
+	if err != nil {
+		utils.SendErrorResponse(w, "HTTP modifiy direction not set")
+		return
+	}
+
+	name, err := utils.PostPara(r, "name")
+	if err != nil {
+		utils.SendErrorResponse(w, "HTTP header name not set")
+		return
+	}
+
+	value, err := utils.PostPara(r, "value")
+	if err != nil && rewriteType == "add" {
+		utils.SendErrorResponse(w, "HTTP header value not set")
+		return
+	}
+
+	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
+	if err != nil {
+		utils.SendErrorResponse(w, "target endpoint not exists")
+		return
+	}
+
+	//Create a Custom Header Defination type
+	var rewriteDirection dynamicproxy.HeaderDirection
+	if direction == "toOrigin" {
+		rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToUpstream
+	} else if direction == "toClient" {
+		rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToDownstream
+	} else {
+		//Unknown direction
+		utils.SendErrorResponse(w, "header rewrite direction not supported")
+		return
+	}
+
+	isRemove := false
+	if rewriteType == "remove" {
+		isRemove = true
+	}
+	headerRewriteDefination := dynamicproxy.UserDefinedHeader{
+		Key:       name,
+		Value:     value,
+		Direction: rewriteDirection,
+		IsRemove:  isRemove,
+	}
+
+	//Create a new custom header object
+	err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefination)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
+		return
+	}
+
+	//Save it (no need reload as header are not handled by dpcore)
+	err = SaveReverseProxyConfig(targetProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to save update")
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+// Remove a header from the target endpoint
+func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
+	domain, err := utils.PostPara(r, "domain")
+	if err != nil {
+		utils.SendErrorResponse(w, "domain or matching rule not defined")
+		return
+	}
+
+	name, err := utils.PostPara(r, "name")
+	if err != nil {
+		utils.SendErrorResponse(w, "HTTP header name not set")
+		return
+	}
+
+	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
+	if err != nil {
+		utils.SendErrorResponse(w, "target endpoint not exists")
+		return
+	}
+
+	err = targetProxyEndpoint.RemoveUserDefinedHeader(name)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to remove header rewrite rule: "+err.Error())
+		return
+	}
+
+	err = SaveReverseProxyConfig(targetProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to save update")
+		return
+	}
+
+	utils.SendOK(w)
+
+}

+ 25 - 0
web/components/httprp.html

@@ -20,6 +20,7 @@
                     <th>Destination</th>
                     <th>Virtual Directory</th>
                     <th>Basic Auth</th>
+                    <th>Rate Limit</th>
                     <th class="no-sort" style="min-width:150px;">Actions</th>
                 </tr>
             </thead>
@@ -107,6 +108,9 @@
                         <td data-label="" editable="true" datatype="basicauth">
                             ${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}
                         </td>
+                        <td data-label="" editable="true" datatype="ratelimit">
+                            ${subd.RequireRateLimit?`<i class="ui green check icon"></i> ${subd.RateLimit}req/s`:`<i class="ui grey remove icon"></i>`}
+                        </td>
                         <td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
                             <div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
                                 <input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
@@ -301,6 +305,23 @@
                     <div>
                 `);
 
+            } else if (datatype == "ratelimit"){
+                let requireRateLimit = payload.RequireRateLimit;
+                let checkstate = "";
+                if (requireRateLimit){
+                    checkstate = "checked";
+                }
+                let rateLimit = payload.RateLimit;
+
+                column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
+                    <input type="checkbox" class="RequireRateLimit" ${checkstate}>
+                        <label>Require Rate Limit</label>
+                    </div>
+                    <div class="ui mini fluid input">
+                        <input type="number" class="RateLimit" value="${rateLimit}" placeholder="100" min="1" max="1000" >
+                    </div>
+                `);
+
             }else if (datatype == 'action'){
                 column.empty().append(`
                 <button title="Save" onclick="saveProxyInlineEdit('${uuid.hexEncode()}');" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui green save icon"></i></button>
@@ -348,6 +369,8 @@
         let requireTLS = $(row).find(".RequireTLS")[0].checked;
         let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
         let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
+        let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
+        let rateLimit = $(row).find(".RateLimit").val();
         let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
         let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
         console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
@@ -364,6 +387,8 @@
                 "tlsval": skipCertValidations,
                 "bpwsorg" : bypassWebsocketOrigin,
                 "bauth" :requireBasicAuth,
+                "rate" :requireRateLimit,
+                "ratenum" :rateLimit,
             },
             success: function(data){
                 if (data.error !== undefined){

+ 25 - 0
web/components/rules.html

@@ -73,6 +73,17 @@
                                         <label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
                                     </div>
                                 </div>
+                                <div class="field">
+                                    <div class="ui checkbox">
+                                        <input type="checkbox" id="requireRateLimit">
+                                        <label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
+                                    </div>
+                                </div>
+                                <div class="field">
+                                    <label>Rate Limit</label>
+                                    <input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
+                                    <small>The Rate Limit is applied to the whole proxy endpoint. If the number of requests exceeds the limit, the proxy will return a 429 error code.</small>
+                                </div>
                                 <div class="field">
                                     <div class="ui checkbox">
                                         <input type="checkbox" id="requireBasicAuth">
@@ -147,6 +158,8 @@
         var skipTLSValidation = $("#skipTLSValidation")[0].checked;
         var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
         var requireBasicAuth = $("#requireBasicAuth")[0].checked;
+        var proxyRateLimit = $("#proxyRateLimit").val();
+        var requireRateLimit = $("#requireRateLimit")[0].checked;
         var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
         var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
         
@@ -176,6 +189,8 @@
                 bpwsorg: skipWebSocketOriginCheck,
                 bypassGlobalTLS: bypassGlobalTLS,
                 bauth: requireBasicAuth,
+                rate: requireRateLimit,
+                ratenum: proxyRateLimit,
                 cred: JSON.stringify(credentials),
                 access: accessRuleToUse,
             },
@@ -264,6 +279,16 @@
     }
     $("#requireBasicAuth").on('change', toggleBasicAuth);
     toggleBasicAuth();
+    
+    function toggleRateLimit() {
+        if ($("#requireRateLimit").parent().checkbox("is checked")) {
+            $("#proxyRateLimit").parent().removeClass("disabled");
+        } else {
+            $("#proxyRateLimit").parent().addClass("disabled");
+        }
+    }
+    $("#requireRateLimit").on('change', toggleRateLimit);
+    toggleRateLimit();
 
 
     /*