mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Added load balancer (wip)
+ Added support for multiple upstreams + Added load balancer + Added upstream abstraction in endpoint + Added load balancer structure + Added breaking change auto-updater + Added uptime monitor proxy type definitions + Added upstream editor UI + Fixed charset bug in many snippets HTML files
This commit is contained in:
parent
7e62fef879
commit
2aa35cbe6d
@ -61,6 +61,12 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener)
|
||||
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
|
||||
authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange)
|
||||
//Reverse proxy upstream (load balance) APIs
|
||||
authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate)
|
||||
authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete)
|
||||
//Reverse proxy virtual directory APIs
|
||||
authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
|
||||
authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
@ -79,7 +80,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
||||
return errors.New("not supported proxy type")
|
||||
}
|
||||
|
||||
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+thisConfigEndpoint.Domain+" routing rule loaded", nil)
|
||||
SystemWideLogger.PrintAndLog("Proxy", thisConfigEndpoint.RootOrMatchingDomain+" -> "+loadbalance.GetUpstreamsAsString(thisConfigEndpoint.ActiveOrigins)+" routing rule loaded", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -130,12 +131,18 @@ func RemoveReverseProxyConfig(endpoint string) error {
|
||||
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
||||
//Default settings
|
||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
||||
ProxyType: dynamicproxy.ProxyType_Root,
|
||||
RootOrMatchingDomain: "/",
|
||||
Domain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||
RequireTLS: false,
|
||||
ProxyType: dynamicproxy.ProxyType_Root,
|
||||
RootOrMatchingDomain: "/",
|
||||
ActiveOrigins: []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||
RequireTLS: false,
|
||||
SkipCertValidations: false,
|
||||
Weight: 0,
|
||||
},
|
||||
},
|
||||
InactiveOrigins: []*loadbalance.Upstream{},
|
||||
BypassGlobalTLS: false,
|
||||
SkipCertValidations: false,
|
||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||
RequireBasicAuth: false,
|
||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||
|
24
src/main.go
24
src/main.go
@ -32,6 +32,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/statistic/analytic"
|
||||
"imuslab.com/zoraxy/mod/streamproxy"
|
||||
"imuslab.com/zoraxy/mod/tlscert"
|
||||
"imuslab.com/zoraxy/mod/update"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
"imuslab.com/zoraxy/mod/webserv"
|
||||
@ -52,6 +53,7 @@ var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high spee
|
||||
var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||
var allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||
var logOutputToFile = flag.Bool("log", true, "Log terminal output to file")
|
||||
var updateMode = flag.Int("update", 0, "Version number (usually the version before you update Zoraxy) to start accumulation update. To update v3.0.7 to latest, use -update=307")
|
||||
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
@ -69,11 +71,11 @@ var (
|
||||
/*
|
||||
Handler Modules
|
||||
*/
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
loadbalancer *loadbalance.RouteManager //Load balancer manager to get routing targets from proxy rules
|
||||
sysdb *database.Database //System database
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
|
||||
pathRuleHandler *pathrule.Handler //Handle specific path blocking or custom headers
|
||||
geodbStore *geodb.Store //GeoIP database, for resolving IP into country code
|
||||
accessController *access.Controller //Access controller, handle black list and white list
|
||||
@ -88,6 +90,7 @@ var (
|
||||
acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking
|
||||
staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs
|
||||
forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser
|
||||
loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing
|
||||
|
||||
//Helper modules
|
||||
EmailSender *email.Sender //Email sender that handle email sending
|
||||
@ -144,6 +147,17 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *updateMode > 306 {
|
||||
fmt.Println("Entering Update Mode")
|
||||
update.RunConfigUpdate(*updateMode, update.GetVersionIntFromVersionNumber(version))
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if !utils.ValidateListeningAddress(*webUIPort) {
|
||||
fmt.Println("Malformed -port (listening address) paramter. Do you mean -port=:" + *webUIPort + "?")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
SetupCloseHandler()
|
||||
|
||||
//Read or create the system uuid
|
||||
|
@ -448,7 +448,12 @@ func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
domains := strings.Split(domainPara, ",")
|
||||
result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
|
||||
//Clean spaces in front or behind each domain
|
||||
cleanedDomains := []string{}
|
||||
for _, domain := range domains {
|
||||
cleanedDomains = append(cleanedDomains, strings.TrimSpace(domain))
|
||||
}
|
||||
result, err := a.ObtainCert(cleanedDomains, filename, email, ca, caUrl, skipTLS, dns)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, jsonEscape(err.Error()))
|
||||
return
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
- Access Router
|
||||
- Blacklist
|
||||
- Whitelist
|
||||
- Rate Limitor
|
||||
- Basic Auth
|
||||
- Vitrual Directory Proxy
|
||||
- Subdomain Proxy
|
||||
@ -30,7 +31,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
Special Routing Rules, bypass most of the limitations
|
||||
*/
|
||||
//Check if there are external routing rule matches.
|
||||
//Check if there are external routing rule (rr) matches.
|
||||
//If yes, route them via external rr
|
||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||
if matchedRoutingRule != nil {
|
||||
@ -45,7 +46,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
//Check if this is a redirection url
|
||||
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
|
||||
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
|
||||
h.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
||||
h.Parent.logRequest(r, statusCode != 500, statusCode, "redirect", "")
|
||||
return
|
||||
}
|
||||
|
||||
@ -193,12 +194,12 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
hostname := parsedURL.Hostname()
|
||||
if hostname == domainOnly {
|
||||
h.logRequest(r, false, 500, "root-redirect", domainOnly)
|
||||
h.Parent.logRequest(r, false, 500, "root-redirect", domainOnly)
|
||||
http.Error(w, "Loopback redirects due to invalid settings", 500)
|
||||
return
|
||||
}
|
||||
|
||||
h.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||
h.Parent.logRequest(r, false, 307, "root-redirect", domainOnly)
|
||||
http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
|
||||
case DefaultSite_NotFoundPage:
|
||||
//Serve the not found page, use template if exists
|
||||
|
@ -24,7 +24,7 @@ func (h *ProxyHandler) handleAccessRouting(ruleID string, w http.ResponseWriter,
|
||||
|
||||
isBlocked, blockedReason := accessRequestBlocked(accessRule, h.Parent.Option.WebDirectory, w, r)
|
||||
if isBlocked {
|
||||
h.logRequest(r, false, 403, blockedReason, "")
|
||||
h.Parent.logRequest(r, false, 403, blockedReason, "")
|
||||
}
|
||||
return isBlocked
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
func (h *ProxyHandler) handleBasicAuthRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
err := handleBasicAuth(w, r, pe)
|
||||
if err != nil {
|
||||
h.logRequest(r, false, 401, "host", pe.Domain)
|
||||
h.Parent.logRequest(r, false, 401, "host", r.URL.Hostname())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ var hopHeaders = []string{
|
||||
"Te", // canonicalized version of "TE"
|
||||
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Transfer-Encoding",
|
||||
//"Upgrade",
|
||||
//"Upgrade", // handled by websocket proxy in higher layer abstraction
|
||||
}
|
||||
|
||||
// Copy response from src to dst with given flush interval, reference from httputil.ReverseProxy
|
||||
|
@ -28,6 +28,7 @@ func NewDynamicProxy(option RouterOption) (*Router, error) {
|
||||
Running: false,
|
||||
server: nil,
|
||||
routingRules: []*RoutingRule{},
|
||||
loadBalancer: option.LoadBalancer,
|
||||
rateLimitCounter: RequestCountPerIpTable{},
|
||||
}
|
||||
|
||||
@ -150,10 +151,16 @@ func (router *Router) StartProxyService() error {
|
||||
}
|
||||
}
|
||||
|
||||
sep.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: sep.Domain,
|
||||
selectedUpstream, err := router.loadBalancer.GetRequestUpstreamTarget(r, sep.ActiveOrigins)
|
||||
if err != nil {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
||||
}
|
||||
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: sep.RequireTLS,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
PathPrefix: "",
|
||||
Version: sep.parent.Option.HostVersion,
|
||||
})
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -133,6 +134,92 @@ func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint)
|
||||
return readyRoutingRule, nil
|
||||
}
|
||||
|
||||
/* Upstream related wrapper functions */
|
||||
//Check if there already exists another upstream with identical origin
|
||||
func (ep *ProxyEndpoint) UpstreamOriginExists(originURL string) bool {
|
||||
for _, origin := range ep.ActiveOrigins {
|
||||
if origin.OriginIpOrDomain == originURL {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, origin := range ep.InactiveOrigins {
|
||||
if origin.OriginIpOrDomain == originURL {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get a upstream origin from given origin ip or domain
|
||||
func (ep *ProxyEndpoint) GetUpstreamOriginByMatchingIP(originIpOrDomain string) (*loadbalance.Upstream, error) {
|
||||
for _, origin := range ep.ActiveOrigins {
|
||||
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||
return origin, nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, origin := range ep.InactiveOrigins {
|
||||
if origin.OriginIpOrDomain == originIpOrDomain {
|
||||
return origin, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("target upstream origin not found")
|
||||
}
|
||||
|
||||
// Add upstream to endpoint and update it to runtime
|
||||
func (ep *ProxyEndpoint) AddUpstreamOrigin(newOrigin *loadbalance.Upstream, activate bool) error {
|
||||
//Check if the upstream already exists
|
||||
if ep.UpstreamOriginExists(newOrigin.OriginIpOrDomain) {
|
||||
return errors.New("upstream with same origin already exists")
|
||||
}
|
||||
|
||||
if activate {
|
||||
//Add it to the active origin list
|
||||
err := newOrigin.StartProxy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ep.ActiveOrigins = append(ep.ActiveOrigins, newOrigin)
|
||||
} else {
|
||||
//Add to inactive origin list
|
||||
ep.InactiveOrigins = append(ep.InactiveOrigins, newOrigin)
|
||||
}
|
||||
|
||||
ep.UpdateToRuntime()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove upstream from endpoint and update it to runtime
|
||||
func (ep *ProxyEndpoint) RemoveUpstreamOrigin(originIpOrDomain string) error {
|
||||
//Just to make sure there are no spaces
|
||||
originIpOrDomain = strings.TrimSpace(originIpOrDomain)
|
||||
|
||||
//Check if the upstream already been removed
|
||||
if !ep.UpstreamOriginExists(originIpOrDomain) {
|
||||
//Not exists in the first place
|
||||
return nil
|
||||
}
|
||||
|
||||
newActiveOriginList := []*loadbalance.Upstream{}
|
||||
for _, origin := range ep.ActiveOrigins {
|
||||
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||
newActiveOriginList = append(newActiveOriginList, origin)
|
||||
}
|
||||
}
|
||||
|
||||
newInactiveOriginList := []*loadbalance.Upstream{}
|
||||
for _, origin := range ep.InactiveOrigins {
|
||||
if origin.OriginIpOrDomain != originIpOrDomain {
|
||||
newInactiveOriginList = append(newInactiveOriginList, origin)
|
||||
}
|
||||
}
|
||||
//Ok, set the origin list to the new one
|
||||
ep.ActiveOrigins = newActiveOriginList
|
||||
ep.InactiveOrigins = newInactiveOriginList
|
||||
ep.UpdateToRuntime()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the proxy endpoint hostname or alias name contains subdomain wildcard
|
||||
func (ep *ProxyEndpoint) ContainsWildcardName(skipAliasCheck bool) bool {
|
||||
hostname := ep.RootOrMatchingDomain
|
||||
|
@ -1,9 +1,13 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -12,49 +16,75 @@ import (
|
||||
Handleing load balance request for upstream destinations
|
||||
*/
|
||||
|
||||
type BalancePolicy int
|
||||
|
||||
const (
|
||||
BalancePolicy_RoundRobin BalancePolicy = 0 //Round robin, will ignore upstream if down
|
||||
BalancePolicy_Fallback BalancePolicy = 1 //Fallback only. Will only switch to next node if the first one failed
|
||||
BalancePolicy_Random BalancePolicy = 2 //Random, randomly pick one from the list that is online
|
||||
BalancePolicy_GeoRegion BalancePolicy = 3 //Use the one defined for this geo-location, when down, pick the next avaible node
|
||||
)
|
||||
|
||||
type LoadBalanceRule struct {
|
||||
Upstreams []string //Reverse proxy upstream servers
|
||||
LoadBalancePolicy BalancePolicy //Policy in deciding which target IP to proxy
|
||||
UseRegionLock bool //If this is enabled with BalancePolicy_Geo, when the main site failed, it will not pick another node
|
||||
UseStickySession bool //Use sticky session, if you are serving EU countries, make sure to add the "Do you want cookie" warning
|
||||
|
||||
parent *RouteManager
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
||||
UptimeMonitor *uptime.Monitor //For checking if the target is online, this might be nil when the module starts
|
||||
UseActiveHealthCheck bool //Use active health check, default to false
|
||||
Geodb *geodb.Store //GeoIP resolver for checking incoming request origin country
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
type RouteManager struct {
|
||||
Options Options
|
||||
Logger *logger.Logger
|
||||
LoadBalanceMap sync.Map //Sync map to store the last load balance state of a given node
|
||||
OnlineStatusMap sync.Map //Sync map to store the online status of a given ip address or domain name
|
||||
onlineStatusTickerStop chan bool //Stopping channel for the online status pinger
|
||||
Options Options //Options for the load balancer
|
||||
}
|
||||
|
||||
// Create a new load balance route manager
|
||||
func NewRouteManager(options *Options, logger *logger.Logger) *RouteManager {
|
||||
newManager := RouteManager{
|
||||
Options: *options,
|
||||
Logger: logger,
|
||||
/* Upstream or Origin Server */
|
||||
type Upstream struct {
|
||||
//Upstream Proxy Configs
|
||||
OriginIpOrDomain string //Target IP address or domain name with port
|
||||
RequireTLS bool //Require TLS connection
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Load balancing configs
|
||||
Weight int //Random weight for round robin, 0 for fallback only
|
||||
MaxConn int //Maxmium connection to this server, 0 for unlimited
|
||||
|
||||
currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
|
||||
proxy *dpcore.ReverseProxy
|
||||
}
|
||||
|
||||
// Create a new load balancer
|
||||
func NewLoadBalancer(options *Options) *RouteManager {
|
||||
onlineStatusCheckerStopChan := make(chan bool)
|
||||
|
||||
return &RouteManager{
|
||||
LoadBalanceMap: sync.Map{},
|
||||
OnlineStatusMap: sync.Map{},
|
||||
onlineStatusTickerStop: onlineStatusCheckerStopChan,
|
||||
Options: *options,
|
||||
}
|
||||
logger.PrintAndLog("INFO", "Load Balance Route Manager started", nil)
|
||||
return &newManager
|
||||
}
|
||||
|
||||
func (b *LoadBalanceRule) GetProxyTargetIP() {
|
||||
//TODO: Implement get proxy target IP logic here
|
||||
// UpstreamsReady checks if the group of upstreams contains at least one
|
||||
// origin server that is ready
|
||||
func (m *RouteManager) UpstreamsReady(upstreams []*Upstream) bool {
|
||||
for _, upstream := range upstreams {
|
||||
if upstream.IsReady() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String format and convert a list of upstream into a string representations
|
||||
func GetUpstreamsAsString(upstreams []*Upstream) string {
|
||||
targets := []string{}
|
||||
for _, upstream := range upstreams {
|
||||
targets = append(targets, upstream.String())
|
||||
}
|
||||
return strings.Join(targets, ", ")
|
||||
}
|
||||
|
||||
func (m *RouteManager) Close() {
|
||||
if m.onlineStatusTickerStop != nil {
|
||||
m.onlineStatusTickerStop <- true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Print debug message
|
||||
func (m *RouteManager) debugPrint(message string, err error) {
|
||||
m.Logger.PrintAndLog("LB", message, err)
|
||||
m.Options.Logger.PrintAndLog("LoadBalancer", message, err)
|
||||
}
|
||||
|
70
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
70
src/mod/dynamicproxy/loadbalance/onlineStatus.go
Normal file
@ -0,0 +1,70 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Return the last ping status to see if the target is online
|
||||
func (m *RouteManager) IsTargetOnline(matchingDomainOrIp string) bool {
|
||||
value, ok := m.LoadBalanceMap.Load(matchingDomainOrIp)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
isOnline, ok := value.(bool)
|
||||
return ok && isOnline
|
||||
}
|
||||
|
||||
func (m *RouteManager) SetTargetOffline() {
|
||||
|
||||
}
|
||||
|
||||
// Ping a target to see if it is online
|
||||
func PingTarget(targetMatchingDomainOrIp string, requireTLS bool) bool {
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
url := targetMatchingDomainOrIp
|
||||
if requireTLS {
|
||||
url = "https://" + url
|
||||
} else {
|
||||
url = "http://" + url
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode >= 200 && resp.StatusCode <= 600
|
||||
}
|
||||
|
||||
// StartHeartbeats start pinging each server every minutes to make sure all targets are online
|
||||
// Active mode only
|
||||
/*
|
||||
func (m *RouteManager) StartHeartbeats(pingTargets []*FallbackProxyTarget) {
|
||||
ticker := time.NewTicker(1 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
fmt.Println("Heartbeat started")
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-m.onlineStatusTickerStop:
|
||||
ticker.Stop()
|
||||
return
|
||||
case <-ticker.C:
|
||||
for _, target := range pingTargets {
|
||||
go func(target *FallbackProxyTarget) {
|
||||
isOnline := PingTarget(target.MatchingDomainOrIp, target.RequireTLS)
|
||||
m.LoadBalanceMap.Store(target.MatchingDomainOrIp, isOnline)
|
||||
}(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
*/
|
26
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
26
src/mod/dynamicproxy/loadbalance/originPicker.go
Normal file
@ -0,0 +1,26 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/*
|
||||
Origin Picker
|
||||
|
||||
This script contains the code to pick the best origin
|
||||
by this request.
|
||||
*/
|
||||
|
||||
// GetRequestUpstreamTarget return the upstream target where this
|
||||
// request should be routed
|
||||
func (m *RouteManager) GetRequestUpstreamTarget(r *http.Request, origins []*Upstream) (*Upstream, error) {
|
||||
if len(origins) == 0 {
|
||||
return nil, errors.New("no upstream is defined for this host")
|
||||
}
|
||||
|
||||
//TODO: Add upstream picking algorithm here
|
||||
fmt.Println("DEBUG: Picking origin " + origins[0].OriginIpOrDomain)
|
||||
return origins[0], nil
|
||||
}
|
75
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
75
src/mod/dynamicproxy/loadbalance/upstream.go
Normal file
@ -0,0 +1,75 @@
|
||||
package loadbalance
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
)
|
||||
|
||||
// StartProxy create and start a HTTP proxy using dpcore
|
||||
// Example of webProxyEndpoint: https://example.com:443 or http://192.168.1.100:8080
|
||||
func (u *Upstream) StartProxy() error {
|
||||
//Filter the tailing slash if any
|
||||
domain := u.OriginIpOrDomain
|
||||
if len(domain) == 0 {
|
||||
return errors.New("invalid endpoint config")
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
|
||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||
//TLS is not hardcoded in proxy target domain
|
||||
if u.RequireTLS {
|
||||
domain = "https://" + domain
|
||||
} else {
|
||||
domain = "http://" + domain
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy agent for this upstream
|
||||
path, err := url.Parse(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||
IgnoreTLSVerification: u.SkipCertValidations,
|
||||
})
|
||||
|
||||
u.proxy = proxy
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsReady return the proxy ready state of the upstream server
|
||||
// Return false if StartProxy() is not called on this upstream before
|
||||
func (u *Upstream) IsReady() bool {
|
||||
return u.proxy != nil
|
||||
}
|
||||
|
||||
// Clone return a new deep copy object of the identical upstream
|
||||
func (u *Upstream) Clone() *Upstream {
|
||||
newUpstream := Upstream{}
|
||||
js, _ := json.Marshal(u)
|
||||
json.Unmarshal(js, &newUpstream)
|
||||
return &newUpstream
|
||||
}
|
||||
|
||||
// ServeHTTP uses this upstream proxy router to route the current request
|
||||
func (u *Upstream) ServeHTTP(w http.ResponseWriter, r *http.Request, rrr *dpcore.ResponseRewriteRuleSet) error {
|
||||
//Auto rewrite to upstream origin if not set
|
||||
if rrr.ProxyDomain == "" {
|
||||
rrr.ProxyDomain = u.OriginIpOrDomain
|
||||
}
|
||||
|
||||
return u.proxy.ServeHTTP(w, r, rrr)
|
||||
}
|
||||
|
||||
// String return the string representations of endpoints in this upstream
|
||||
func (u *Upstream) String() string {
|
||||
return u.OriginIpOrDomain
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
"imuslab.com/zoraxy/mod/websocketproxy"
|
||||
)
|
||||
|
||||
// Check if the request URI matches any of the proxy endpoint
|
||||
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
|
||||
var targetProxyEndpoint *ProxyEndpoint = nil
|
||||
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
|
||||
@ -30,6 +31,7 @@ func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *P
|
||||
return targetProxyEndpoint
|
||||
}
|
||||
|
||||
// Get the proxy endpoint from hostname, which might includes checking of wildcard certificates
|
||||
func (router *Router) getProxyEndpointFromHostname(hostname string) *ProxyEndpoint {
|
||||
var targetSubdomainEndpoint *ProxyEndpoint = nil
|
||||
ep, ok := router.ProxyEndpoints.Load(hostname)
|
||||
@ -110,12 +112,18 @@ func (router *Router) rewriteURL(rooturl string, requestURL string) string {
|
||||
func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
|
||||
r.Header.Set("X-Forwarded-Host", r.Host)
|
||||
r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
|
||||
|
||||
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(r, target.ActiveOrigins)
|
||||
if err != nil {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
|
||||
return
|
||||
}
|
||||
requestURL := r.URL.String()
|
||||
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
|
||||
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
wsRedirectionEndpoint := target.Domain
|
||||
wsRedirectionEndpoint := selectedUpstream.OriginIpOrDomain
|
||||
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
|
||||
//Append / to the end of the redirection endpoint if not exists
|
||||
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
|
||||
@ -125,13 +133,13 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
requestURL = requestURL[1:]
|
||||
}
|
||||
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
|
||||
if target.RequireTLS {
|
||||
if selectedUpstream.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||
}
|
||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
||||
h.Parent.logRequest(r, true, 101, "subdomain-websocket", selectedUpstream.OriginIpOrDomain)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
||||
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
||||
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
@ -148,10 +156,10 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
//Build downstream and upstream header rules
|
||||
upstreamHeaders, downstreamHeaders := target.SplitInboundOutboundHeaders()
|
||||
|
||||
err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: target.Domain,
|
||||
err = selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: target.RequireTLS,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
NoCache: h.Parent.Option.NoCache,
|
||||
PathPrefix: "",
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
@ -164,15 +172,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
if errors.As(err, &dnsError) {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 404, "subdomain-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 404, "subdomain-http", r.URL.Hostname())
|
||||
} else {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 521, "subdomain-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 521, "subdomain-http", r.URL.Hostname())
|
||||
}
|
||||
}
|
||||
|
||||
h.logRequest(r, true, 200, "subdomain-http", target.Domain)
|
||||
h.Parent.logRequest(r, true, 200, "subdomain-http", r.URL.Hostname())
|
||||
}
|
||||
|
||||
// Handle vdir type request
|
||||
@ -194,10 +202,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
if target.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||
}
|
||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
||||
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
@ -229,23 +237,23 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
if errors.As(err, &dnsError) {
|
||||
http.ServeFile(w, r, "./web/hosterror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 404, "vdir-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 404, "vdir-http", target.Domain)
|
||||
} else {
|
||||
http.ServeFile(w, r, "./web/rperror.html")
|
||||
log.Println(err.Error())
|
||||
h.logRequest(r, false, 521, "vdir-http", target.Domain)
|
||||
h.Parent.logRequest(r, false, 521, "vdir-http", target.Domain)
|
||||
}
|
||||
}
|
||||
h.logRequest(r, true, 200, "vdir-http", target.Domain)
|
||||
h.Parent.logRequest(r, true, 200, "vdir-http", target.Domain)
|
||||
|
||||
}
|
||||
|
||||
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
||||
if h.Parent.Option.StatisticCollector != nil {
|
||||
func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, forwardType string, target string) {
|
||||
if router.Option.StatisticCollector != nil {
|
||||
go func() {
|
||||
requestInfo := statistic.RequestInfo{
|
||||
IpAddr: netutils.GetRequesterIP(r),
|
||||
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
RequestOriginalCountryISOCode: router.Option.GeodbStore.GetRequesterCountryISOCode(r),
|
||||
Succ: succ,
|
||||
StatusCode: statusCode,
|
||||
ForwardType: forwardType,
|
||||
@ -254,7 +262,7 @@ func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, fo
|
||||
RequestURL: r.Host + r.RequestURI,
|
||||
Target: target,
|
||||
}
|
||||
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||
router.Option.StatisticCollector.RecordRequest(requestInfo)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func (t *RequestCountPerIpTable) Clear() {
|
||||
func (h *ProxyHandler) handleRateLimitRouting(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint) error {
|
||||
err := h.Parent.handleRateLimit(w, r, pe)
|
||||
if err != nil {
|
||||
h.logRequest(r, false, 429, "ratelimit", pe.Domain)
|
||||
h.Parent.logRequest(r, false, 429, "ratelimit", r.URL.Hostname())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package dynamicproxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@ -17,41 +18,18 @@ import (
|
||||
|
||||
// Prepare proxy route generate a proxy handler service object for your endpoint
|
||||
func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint, error) {
|
||||
//Filter the tailing slash if any
|
||||
domain := endpoint.Domain
|
||||
if len(domain) == 0 {
|
||||
return nil, errors.New("invalid endpoint config")
|
||||
}
|
||||
if domain[len(domain)-1:] == "/" {
|
||||
domain = domain[:len(domain)-1]
|
||||
}
|
||||
endpoint.Domain = domain
|
||||
|
||||
//Parse the web proxy endpoint
|
||||
webProxyEndpoint := domain
|
||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||
//TLS is not hardcoded in proxy target domain
|
||||
if endpoint.RequireTLS {
|
||||
webProxyEndpoint = "https://" + webProxyEndpoint
|
||||
} else {
|
||||
webProxyEndpoint = "http://" + webProxyEndpoint
|
||||
for _, thisOrigin := range endpoint.ActiveOrigins {
|
||||
//Create the proxy routing handler
|
||||
err := thisOrigin.StartProxy()
|
||||
if err != nil {
|
||||
log.Println("Unable to setup upstream " + thisOrigin.OriginIpOrDomain + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
//Create a new proxy agent for this root
|
||||
path, err := url.Parse(webProxyEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Create the proxy routing handler
|
||||
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
|
||||
IgnoreTLSVerification: endpoint.SkipCertValidations,
|
||||
})
|
||||
endpoint.proxy = proxy
|
||||
endpoint.parent = router
|
||||
|
||||
//Prepare proxy routing hjandler for each of the virtual directories
|
||||
//Prepare proxy routing handler for each of the virtual directories
|
||||
for _, vdir := range endpoint.VirtualDirectories {
|
||||
domain := vdir.Domain
|
||||
if len(domain) == 0 {
|
||||
@ -63,7 +41,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
||||
}
|
||||
|
||||
//Parse the web proxy endpoint
|
||||
webProxyEndpoint = domain
|
||||
webProxyEndpoint := domain
|
||||
if !strings.HasPrefix("http://", domain) && !strings.HasPrefix("https://", domain) {
|
||||
//TLS is not hardcoded in proxy target domain
|
||||
if vdir.RequireTLS {
|
||||
@ -90,7 +68,7 @@ func (router *Router) PrepareProxyRoute(endpoint *ProxyEndpoint) (*ProxyEndpoint
|
||||
|
||||
// Add Proxy Route to current runtime. Call to PrepareProxyRoute before adding to runtime
|
||||
func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||
if endpoint.proxy == nil {
|
||||
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||
//This endpoint is not prepared
|
||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||
}
|
||||
@ -101,7 +79,7 @@ func (router *Router) AddProxyRouteToRuntime(endpoint *ProxyEndpoint) error {
|
||||
|
||||
// Set given Proxy Route as Root. Call to PrepareProxyRoute before adding to runtime
|
||||
func (router *Router) SetProxyRouteAsRoot(endpoint *ProxyEndpoint) error {
|
||||
if endpoint.proxy == nil {
|
||||
if !router.loadBalancer.UpstreamsReady(endpoint.ActiveOrigins) {
|
||||
//This endpoint is not prepared
|
||||
return errors.New("proxy endpoint not ready. Use PrepareProxyRoute before adding to runtime")
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/access"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
@ -25,23 +26,26 @@ type ProxyHandler struct {
|
||||
Parent *Router
|
||||
}
|
||||
|
||||
/* Router Object Options */
|
||||
type RouterOption struct {
|
||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||
HostVersion string //The version of Zoraxy, use for heading mod
|
||||
Port int //Incoming port
|
||||
UseTls bool //Use TLS to serve incoming requsts
|
||||
ForceTLSLatest bool //Force TLS1.2 or above
|
||||
NoCache bool //Force set Cache-Control: no-store
|
||||
ListenOnPort80 bool //Enable port 80 http listener
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager
|
||||
RedirectRuleTable *redirection.RuleTable
|
||||
GeodbStore *geodb.Store //GeoIP resolver
|
||||
AccessController *access.Controller //Blacklist / whitelist controller
|
||||
StatisticCollector *statistic.Collector
|
||||
WebDirectory string //The static web server directory containing the templates folder
|
||||
HostUUID string //The UUID of Zoraxy, use for heading mod
|
||||
HostVersion string //The version of Zoraxy, use for heading mod
|
||||
Port int //Incoming port
|
||||
UseTls bool //Use TLS to serve incoming requsts
|
||||
ForceTLSLatest bool //Force TLS1.2 or above
|
||||
NoCache bool //Force set Cache-Control: no-store
|
||||
ListenOnPort80 bool //Enable port 80 http listener
|
||||
ForceHttpsRedirect bool //Force redirection of http to https endpoint
|
||||
TlsManager *tlscert.Manager //TLS manager for serving SAN certificates
|
||||
RedirectRuleTable *redirection.RuleTable //Redirection rules handler and table
|
||||
GeodbStore *geodb.Store //GeoIP resolver
|
||||
AccessController *access.Controller //Blacklist / whitelist controller
|
||||
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
|
||||
WebDirectory string //The static web server directory containing the templates folder
|
||||
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
|
||||
}
|
||||
|
||||
/* Router Object */
|
||||
type Router struct {
|
||||
Option *RouterOption
|
||||
ProxyEndpoints *sync.Map
|
||||
@ -50,6 +54,7 @@ type Router struct {
|
||||
mux http.Handler
|
||||
server *http.Server
|
||||
tlsListener net.Listener
|
||||
loadBalancer *loadbalance.RouteManager //Load balancer routing manager
|
||||
routingRules []*RoutingRule
|
||||
|
||||
tlsRedirectStop chan bool //Stop channel for tls redirection server
|
||||
@ -57,6 +62,7 @@ type Router struct {
|
||||
rateLimitCounter RequestCountPerIpTable //Request counter for rate limter
|
||||
}
|
||||
|
||||
/* Basic Auth Related Data structure*/
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
type BasicAuthCredentials struct {
|
||||
Username string
|
||||
@ -74,6 +80,7 @@ type BasicAuthExceptionRule struct {
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
/* Custom Header Related Data structure */
|
||||
// Header injection direction type
|
||||
type HeaderDirection int
|
||||
|
||||
@ -90,6 +97,8 @@ type UserDefinedHeader struct {
|
||||
IsRemove bool //Instead of set, remove this key instead
|
||||
}
|
||||
|
||||
/* Routing Rule Data Structures */
|
||||
|
||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||
// program structure than directly using ProxyEndpoint
|
||||
type VirtualDirectoryEndpoint struct {
|
||||
@ -104,16 +113,17 @@ type VirtualDirectoryEndpoint struct {
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
Domain string //Domain or IP to proxy to
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
ActiveOrigins []*loadbalance.Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||
InactiveOrigins []*loadbalance.Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||
UseStickySession bool //Use stick session for load balancing
|
||||
UseActiveLoadBalance bool //Use active loadbalancing, default passive
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//TLS/SSL Related
|
||||
RequireTLS bool //Target domain require TLS
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
//Inbound TLS/SSL Related
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||
@ -136,15 +146,12 @@ type ProxyEndpoint struct {
|
||||
//Access Control
|
||||
AccessFilterUUID string //Access filter ID
|
||||
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
|
||||
//Internal Logic Elements
|
||||
parent *Router `json:"-"`
|
||||
proxy *dpcore.ReverseProxy `json:"-"`
|
||||
parent *Router `json:"-"`
|
||||
}
|
||||
|
||||
/*
|
||||
|
44
src/mod/update/update.go
Normal file
44
src/mod/update/update.go
Normal file
@ -0,0 +1,44 @@
|
||||
package update
|
||||
|
||||
/*
|
||||
Update.go
|
||||
|
||||
This module handle cross version updates that contains breaking changes
|
||||
update command should always exit after the update is completed
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
v308 "imuslab.com/zoraxy/mod/update/v308"
|
||||
)
|
||||
|
||||
// Run config update. Version numbers are int. For example
|
||||
// to update 3.0.7 to 3.0.8, use RunConfigUpdate(307, 308)
|
||||
// This function support cross versions updates (e.g. 307 -> 310)
|
||||
func RunConfigUpdate(fromVersion int, toVersion int) {
|
||||
for i := fromVersion; i < toVersion; i++ {
|
||||
oldVersion := fromVersion
|
||||
newVersion := fromVersion + 1
|
||||
fmt.Println("Updating from v", oldVersion, " to v", newVersion)
|
||||
runUpdateRoutineWithVersion(oldVersion, newVersion)
|
||||
}
|
||||
fmt.Println("Update completed")
|
||||
}
|
||||
|
||||
func GetVersionIntFromVersionNumber(version string) int {
|
||||
versionNumberOnly := strings.ReplaceAll(version, ".", "")
|
||||
versionInt, _ := strconv.Atoi(versionNumberOnly)
|
||||
return versionInt
|
||||
}
|
||||
|
||||
func runUpdateRoutineWithVersion(fromVersion int, toVersion int) {
|
||||
if fromVersion == 307 && toVersion == 308 {
|
||||
err := v308.UpdateFrom307To308()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
138
src/mod/update/v308/typedef307.go
Normal file
138
src/mod/update/v308/typedef307.go
Normal file
@ -0,0 +1,138 @@
|
||||
package v308
|
||||
|
||||
/*
|
||||
v307 type definations
|
||||
|
||||
This file wrap up the self-contained data structure
|
||||
for v3.0.7 structure and allow automatic updates
|
||||
for future releases if required
|
||||
*/
|
||||
|
||||
type v307PermissionsPolicy struct {
|
||||
Accelerometer []string `json:"accelerometer"`
|
||||
AmbientLightSensor []string `json:"ambient_light_sensor"`
|
||||
Autoplay []string `json:"autoplay"`
|
||||
Battery []string `json:"battery"`
|
||||
Camera []string `json:"camera"`
|
||||
CrossOriginIsolated []string `json:"cross_origin_isolated"`
|
||||
DisplayCapture []string `json:"display_capture"`
|
||||
DocumentDomain []string `json:"document_domain"`
|
||||
EncryptedMedia []string `json:"encrypted_media"`
|
||||
ExecutionWhileNotRendered []string `json:"execution_while_not_rendered"`
|
||||
ExecutionWhileOutOfView []string `json:"execution_while_out_of_viewport"`
|
||||
Fullscreen []string `json:"fullscreen"`
|
||||
Geolocation []string `json:"geolocation"`
|
||||
Gyroscope []string `json:"gyroscope"`
|
||||
KeyboardMap []string `json:"keyboard_map"`
|
||||
Magnetometer []string `json:"magnetometer"`
|
||||
Microphone []string `json:"microphone"`
|
||||
Midi []string `json:"midi"`
|
||||
NavigationOverride []string `json:"navigation_override"`
|
||||
Payment []string `json:"payment"`
|
||||
PictureInPicture []string `json:"picture_in_picture"`
|
||||
PublicKeyCredentialsGet []string `json:"publickey_credentials_get"`
|
||||
ScreenWakeLock []string `json:"screen_wake_lock"`
|
||||
SyncXHR []string `json:"sync_xhr"`
|
||||
USB []string `json:"usb"`
|
||||
WebShare []string `json:"web_share"`
|
||||
XRSpatialTracking []string `json:"xr_spatial_tracking"`
|
||||
ClipboardRead []string `json:"clipboard_read"`
|
||||
ClipboardWrite []string `json:"clipboard_write"`
|
||||
Gamepad []string `json:"gamepad"`
|
||||
SpeakerSelection []string `json:"speaker_selection"`
|
||||
ConversionMeasurement []string `json:"conversion_measurement"`
|
||||
FocusWithoutUserActivation []string `json:"focus_without_user_activation"`
|
||||
HID []string `json:"hid"`
|
||||
IdleDetection []string `json:"idle_detection"`
|
||||
InterestCohort []string `json:"interest_cohort"`
|
||||
Serial []string `json:"serial"`
|
||||
SyncScript []string `json:"sync_script"`
|
||||
TrustTokenRedemption []string `json:"trust_token_redemption"`
|
||||
Unload []string `json:"unload"`
|
||||
WindowPlacement []string `json:"window_placement"`
|
||||
VerticalScroll []string `json:"vertical_scroll"`
|
||||
}
|
||||
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
type v307BasicAuthCredentials struct {
|
||||
Username string
|
||||
PasswordHash string
|
||||
}
|
||||
|
||||
// Auth credential for basic auth on certain endpoints
|
||||
type v307BasicAuthUnhashedCredentials struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Paths to exclude in basic auth enabled proxy handler
|
||||
type v307BasicAuthExceptionRule struct {
|
||||
PathPrefix string
|
||||
}
|
||||
|
||||
// Header injection direction type
|
||||
type v307HeaderDirection int
|
||||
|
||||
const (
|
||||
HeaderDirection_ZoraxyToUpstream v307HeaderDirection = 0 //Inject (or remove) header to request out-going from Zoraxy to backend server
|
||||
HeaderDirection_ZoraxyToDownstream v307HeaderDirection = 1 //Inject (or remove) header to request out-going from Zoraxy to client (e.g. browser)
|
||||
)
|
||||
|
||||
// User defined headers to add into a proxy endpoint
|
||||
type v307UserDefinedHeader struct {
|
||||
Direction v307HeaderDirection
|
||||
Key string
|
||||
Value string
|
||||
IsRemove bool //Instead of set, remove this key instead
|
||||
}
|
||||
|
||||
// The original proxy endpoint structure from v3.0.7
|
||||
type v307ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
Domain string //Domain or IP to proxy to
|
||||
|
||||
//TLS/SSL Related
|
||||
RequireTLS bool //Target domain require TLS
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||
|
||||
//Authentication
|
||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //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
|
||||
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
}
|
||||
|
||||
// A Virtual Directory endpoint, provide a subset of ProxyEndpoint for better
|
||||
// program structure than directly using ProxyEndpoint
|
||||
type v307VirtualDirectoryEndpoint struct {
|
||||
MatchingPath string //Matching prefix of the request path, also act as key
|
||||
Domain string //Domain or IP to proxy to
|
||||
RequireTLS bool //Target domain require TLS
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
Disabled bool //If the rule is enabled
|
||||
}
|
63
src/mod/update/v308/typedef308.go
Normal file
63
src/mod/update/v308/typedef308.go
Normal file
@ -0,0 +1,63 @@
|
||||
package v308
|
||||
|
||||
/*
|
||||
v308 type definations
|
||||
|
||||
This file wrap up the self-contained data structure
|
||||
for v3.0.8 structure and allow automatic updates
|
||||
for future releases if required
|
||||
|
||||
Some struct are identical as v307 and hence it is not redefined here
|
||||
*/
|
||||
|
||||
/* Upstream or Origin Server */
|
||||
type v308Upstream struct {
|
||||
//Upstream Proxy Configs
|
||||
OriginIpOrDomain string //Target IP address or domain name with port
|
||||
RequireTLS bool //Require TLS connection
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Load balancing configs
|
||||
Weight int //Prirotiy of fallback, set all to 0 for round robin
|
||||
MaxConn int //Maxmium connection to this server
|
||||
}
|
||||
|
||||
// A proxy endpoint record, a general interface for handling inbound routing
|
||||
type v308ProxyEndpoint struct {
|
||||
ProxyType int //The type of this proxy, see const def
|
||||
RootOrMatchingDomain string //Matching domain for host, also act as key
|
||||
MatchingDomainAlias []string //A list of domains that alias to this rule
|
||||
ActiveOrigins []*v308Upstream //Activated Upstream or origin servers IP or domain to proxy to
|
||||
InactiveOrigins []*v308Upstream //Disabled Upstream or origin servers IP or domain to proxy to
|
||||
UseStickySession bool //Use stick session for load balancing
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Inbound TLS/SSL Related
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*v307VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
UserDefinedHeaders []*v307UserDefinedHeader //Custom headers to append when proxying requests from this endpoint
|
||||
HSTSMaxAge int64 //HSTS max age, set to 0 for disable HSTS headers
|
||||
EnablePermissionPolicyHeader bool //Enable injection of permission policy header
|
||||
PermissionPolicy *v307PermissionsPolicy //Permission policy header
|
||||
|
||||
//Authentication
|
||||
RequireBasicAuth bool //Set to true to request basic auth before proxy
|
||||
BasicAuthCredentials []*v307BasicAuthCredentials //Basic auth credentials
|
||||
BasicAuthExceptionRules []*v307BasicAuthExceptionRule //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
|
||||
|
||||
//Fallback routing logic (Special Rule Sets Only)
|
||||
DefaultSiteOption int //Fallback routing logic options
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
}
|
132
src/mod/update/v308/v308.go
Normal file
132
src/mod/update/v308/v308.go
Normal file
@ -0,0 +1,132 @@
|
||||
package v308
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
v3.0.7 update to v3.0.8
|
||||
|
||||
This update function
|
||||
*/
|
||||
|
||||
// Update proxy config files from v3.0.7 to v3.0.8
|
||||
func UpdateFrom307To308() error {
|
||||
|
||||
//Load the configs
|
||||
oldConfigFiles, err := filepath.Glob("./conf/proxy/*.config")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Backup all the files
|
||||
err = os.MkdirAll("./conf/proxy.old/", 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, oldConfigFile := range oldConfigFiles {
|
||||
// Extract the file name from the path
|
||||
fileName := filepath.Base(oldConfigFile)
|
||||
// Construct the backup file path
|
||||
backupFile := filepath.Join("./conf/proxy.old/", fileName)
|
||||
|
||||
// Copy the file to the backup directory
|
||||
err := copyFile(oldConfigFile, backupFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//read the config into the old struct
|
||||
for _, oldConfigFile := range oldConfigFiles {
|
||||
configContent, err := os.ReadFile(oldConfigFile)
|
||||
if err != nil {
|
||||
log.Println("Unable to read config file "+filepath.Base(oldConfigFile), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
thisOldConfigStruct := v307ProxyEndpoint{}
|
||||
err = json.Unmarshal(configContent, &thisOldConfigStruct)
|
||||
if err != nil {
|
||||
log.Println("Unable to parse file "+filepath.Base(oldConfigFile), err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
//Convert the old config to new config
|
||||
newProxyStructure := convertV307ToV308(thisOldConfigStruct)
|
||||
js, _ := json.MarshalIndent(newProxyStructure, "", " ")
|
||||
err = os.WriteFile(oldConfigFile, js, 0775)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertV307ToV308(old v307ProxyEndpoint) v308ProxyEndpoint {
|
||||
// Create a new v308ProxyEndpoint instance
|
||||
|
||||
matchingDomainsSlice := old.MatchingDomainAlias
|
||||
if matchingDomainsSlice == nil {
|
||||
matchingDomainsSlice = []string{}
|
||||
}
|
||||
|
||||
newEndpoint := v308ProxyEndpoint{
|
||||
ProxyType: old.ProxyType,
|
||||
RootOrMatchingDomain: old.RootOrMatchingDomain,
|
||||
MatchingDomainAlias: matchingDomainsSlice,
|
||||
ActiveOrigins: []*v308Upstream{{ // Mapping Domain field to v308Upstream struct
|
||||
OriginIpOrDomain: old.Domain,
|
||||
RequireTLS: old.RequireTLS,
|
||||
SkipCertValidations: old.SkipCertValidations,
|
||||
SkipWebSocketOriginCheck: old.SkipWebSocketOriginCheck,
|
||||
Weight: 1,
|
||||
MaxConn: 0,
|
||||
}},
|
||||
InactiveOrigins: []*v308Upstream{},
|
||||
UseStickySession: false,
|
||||
Disabled: old.Disabled,
|
||||
BypassGlobalTLS: old.BypassGlobalTLS,
|
||||
VirtualDirectories: old.VirtualDirectories,
|
||||
UserDefinedHeaders: old.UserDefinedHeaders,
|
||||
HSTSMaxAge: old.HSTSMaxAge,
|
||||
EnablePermissionPolicyHeader: old.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: old.PermissionPolicy,
|
||||
RequireBasicAuth: old.RequireBasicAuth,
|
||||
BasicAuthCredentials: old.BasicAuthCredentials,
|
||||
BasicAuthExceptionRules: old.BasicAuthExceptionRules,
|
||||
RequireRateLimit: old.RequireRateLimit,
|
||||
RateLimit: old.RateLimit,
|
||||
AccessFilterUUID: old.AccessFilterUUID,
|
||||
DefaultSiteOption: old.DefaultSiteOption,
|
||||
DefaultSiteValue: old.DefaultSiteValue,
|
||||
}
|
||||
|
||||
return newEndpoint
|
||||
}
|
||||
|
||||
// Helper function to copy files
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destinationFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
_, err = io.Copy(destinationFile, sourceFile)
|
||||
return err
|
||||
}
|
@ -23,11 +23,19 @@ type Record struct {
|
||||
Latency int64
|
||||
}
|
||||
|
||||
type ProxyType string
|
||||
|
||||
const (
|
||||
ProxyType_Host ProxyType = "Origin Server"
|
||||
ProxyType_Vdir ProxyType = "Virtual Directory"
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
ID string
|
||||
Name string
|
||||
URL string
|
||||
Protocol string
|
||||
ID string
|
||||
Name string
|
||||
URL string
|
||||
Protocol string
|
||||
ProxyType ProxyType
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@ -141,3 +142,35 @@ func StringInArrayIgnoreCase(arr []string, str string) bool {
|
||||
|
||||
return StringInArray(smallArray, strings.ToLower(str))
|
||||
}
|
||||
|
||||
// Validate if the listening address is correct
|
||||
func ValidateListeningAddress(address string) bool {
|
||||
// Check if the address starts with a colon, indicating it's just a port
|
||||
if strings.HasPrefix(address, ":") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Split the address into host and port parts
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
// Try to parse it as just a port
|
||||
if _, err := strconv.Atoi(address); err == nil {
|
||||
return false // It's just a port number
|
||||
}
|
||||
return false // It's an invalid address
|
||||
}
|
||||
|
||||
// Check if the port part is a valid number
|
||||
if _, err := strconv.Atoi(port); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the host part is a valid IP address or empty (indicating any IP)
|
||||
if host != "" {
|
||||
if net.ParseIP(host) == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
@ -96,6 +97,7 @@ func ReverseProxtInit() {
|
||||
StatisticCollector: statisticCollector,
|
||||
WebDirectory: *staticWebServerRoot,
|
||||
AccessController: accessController,
|
||||
LoadBalancer: loadBalancer,
|
||||
})
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("Proxy", "Unable to create dynamic proxy router", err)
|
||||
@ -145,9 +147,6 @@ func ReverseProxtInit() {
|
||||
MaxRecordsStore: 288, //1 day
|
||||
})
|
||||
|
||||
//Pass the pointer of this uptime monitor into the load balancer
|
||||
loadbalancer.Options.UptimeMonitor = uptimeMonitor
|
||||
|
||||
SystemWideLogger.Println("Uptime Monitor background service started")
|
||||
}()
|
||||
}
|
||||
@ -319,13 +318,21 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
ProxyType: dynamicproxy.ProxyType_Host,
|
||||
RootOrMatchingDomain: rootOrMatchingDomain,
|
||||
MatchingDomainAlias: aliasHostnames,
|
||||
Domain: endpoint,
|
||||
ActiveOrigins: []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: endpoint,
|
||||
RequireTLS: useTLS,
|
||||
SkipCertValidations: skipTlsValidation,
|
||||
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
InactiveOrigins: []*loadbalance.Upstream{},
|
||||
UseStickySession: false, //TODO: Move options to webform
|
||||
|
||||
//TLS
|
||||
RequireTLS: useTLS,
|
||||
BypassGlobalTLS: useBypassGlobalTLS,
|
||||
SkipCertValidations: skipTlsValidation,
|
||||
SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
|
||||
AccessFilterUUID: accessRuleID,
|
||||
BypassGlobalTLS: useBypassGlobalTLS,
|
||||
AccessFilterUUID: accessRuleID,
|
||||
//VDir
|
||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||
//Custom headers
|
||||
@ -375,14 +382,19 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
//Write the root options to file
|
||||
rootRoutingEndpoint := dynamicproxy.ProxyEndpoint{
|
||||
ProxyType: dynamicproxy.ProxyType_Root,
|
||||
RootOrMatchingDomain: "/",
|
||||
Domain: endpoint,
|
||||
RequireTLS: useTLS,
|
||||
BypassGlobalTLS: false,
|
||||
SkipCertValidations: false,
|
||||
SkipWebSocketOriginCheck: true,
|
||||
|
||||
ProxyType: dynamicproxy.ProxyType_Root,
|
||||
RootOrMatchingDomain: "/",
|
||||
ActiveOrigins: []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: endpoint,
|
||||
RequireTLS: useTLS,
|
||||
SkipCertValidations: true,
|
||||
SkipWebSocketOriginCheck: true,
|
||||
Weight: 1,
|
||||
},
|
||||
},
|
||||
InactiveOrigins: []*loadbalance.Upstream{},
|
||||
BypassGlobalTLS: false,
|
||||
DefaultSiteOption: defaultSiteOption,
|
||||
DefaultSiteValue: dsVal,
|
||||
}
|
||||
@ -392,7 +404,11 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
|
||||
err = dynamicProxyRouter.SetProxyRouteAsRoot(preparedRootProxyRoute)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to update default site: "+err.Error())
|
||||
return
|
||||
}
|
||||
proxyEndpointCreated = &rootRoutingEndpoint
|
||||
} else {
|
||||
//Invalid eptype
|
||||
@ -426,24 +442,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
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")
|
||||
useStickySession, _ := utils.PostBool(r, "ss")
|
||||
|
||||
//Load bypass TLS option
|
||||
bpgtls, _ := utils.PostPara(r, "bpgtls")
|
||||
@ -475,20 +479,22 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendErrorResponse(w, "invalid rate limit number")
|
||||
return
|
||||
}
|
||||
|
||||
if requireRateLimit && proxyRateLimit <= 0 {
|
||||
|
||||
if requireRateLimit && proxyRateLimit <= 0 {
|
||||
utils.SendErrorResponse(w, "rate limit number must be greater than 0")
|
||||
return
|
||||
}else if proxyRateLimit < 0 {
|
||||
} else if proxyRateLimit < 0 {
|
||||
proxyRateLimit = 1000
|
||||
}
|
||||
|
||||
// Bypass WebSocket Origin Check
|
||||
strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
|
||||
if strbpwsorg == "" {
|
||||
strbpwsorg = "false"
|
||||
}
|
||||
bypassWebsocketOriginCheck := (strbpwsorg == "true")
|
||||
/*
|
||||
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)
|
||||
@ -499,14 +505,16 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
//Generate a new proxyEndpoint from the new config
|
||||
newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
|
||||
newProxyEndpoint.Domain = endpoint
|
||||
newProxyEndpoint.RequireTLS = useTLS
|
||||
//TODO: Move these into dedicated module
|
||||
//newProxyEndpoint.Domain = endpoint
|
||||
//newProxyEndpoint.RequireTLS = useTLS
|
||||
//newProxyEndpoint.SkipCertValidations = skipTlsValidation
|
||||
//newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
|
||||
newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
|
||||
newProxyEndpoint.SkipCertValidations = skipTlsValidation
|
||||
newProxyEndpoint.RequireBasicAuth = requireBasicAuth
|
||||
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||
newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
|
||||
newProxyEndpoint.UseStickySession = useStickySession
|
||||
|
||||
//Prepare to replace the current routing rule
|
||||
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||
@ -939,7 +947,7 @@ func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Domain < results[j].Domain
|
||||
return results[i].RootOrMatchingDomain < results[j].RootOrMatchingDomain
|
||||
})
|
||||
|
||||
js, _ := json.Marshal(results)
|
||||
@ -1059,15 +1067,20 @@ func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
rootProxyTargetOrigin := ""
|
||||
if len(dynamicProxyRouter.Root.ActiveOrigins) > 0 {
|
||||
rootProxyTargetOrigin = dynamicProxyRouter.Root.ActiveOrigins[0].OriginIpOrDomain
|
||||
}
|
||||
|
||||
//Check if it is identical as proxy root (recursion!)
|
||||
if dynamicProxyRouter.Root == nil || dynamicProxyRouter.Root.Domain == "" {
|
||||
if dynamicProxyRouter.Root == nil || rootProxyTargetOrigin == "" {
|
||||
//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, "/")
|
||||
proxyRoot := strings.TrimSuffix(rootProxyTargetOrigin, "/")
|
||||
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
|
||||
|
10
src/start.go
10
src/start.go
@ -102,10 +102,11 @@ func startupSequence() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
//Create a load balance route manager
|
||||
loadbalancer = loadbalance.NewRouteManager(&loadbalance.Options{
|
||||
Geodb: geodbStore,
|
||||
}, SystemWideLogger)
|
||||
//Create a load balancer
|
||||
loadBalancer = loadbalance.NewLoadBalancer(&loadbalance.Options{
|
||||
Geodb: geodbStore,
|
||||
Logger: SystemWideLogger,
|
||||
})
|
||||
|
||||
//Create the access controller
|
||||
accessController, err = access.NewAccessController(&access.Options{
|
||||
@ -291,5 +292,4 @@ func finalSequence() {
|
||||
|
||||
//Inject routing rules
|
||||
registerBuildInRoutingRules()
|
||||
|
||||
}
|
||||
|
274
src/upstreams.go
Normal file
274
src/upstreams.go
Normal file
@ -0,0 +1,274 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
Upstreams.go
|
||||
|
||||
This script handle upstream and load balancer
|
||||
related API
|
||||
*/
|
||||
|
||||
// List upstreams from a endpoint
|
||||
func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) {
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
}
|
||||
|
||||
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not found")
|
||||
return
|
||||
}
|
||||
|
||||
activeUpstreams := targetEndpoint.ActiveOrigins
|
||||
inactiveUpstreams := targetEndpoint.InactiveOrigins
|
||||
// Sort the upstreams slice by weight, then by origin domain alphabetically
|
||||
sort.Slice(activeUpstreams, func(i, j int) bool {
|
||||
if activeUpstreams[i].Weight != activeUpstreams[j].Weight {
|
||||
return activeUpstreams[i].Weight > activeUpstreams[j].Weight
|
||||
}
|
||||
return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain
|
||||
})
|
||||
|
||||
sort.Slice(inactiveUpstreams, func(i, j int) bool {
|
||||
if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight {
|
||||
return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight
|
||||
}
|
||||
return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain
|
||||
})
|
||||
|
||||
type UpstreamCombinedList struct {
|
||||
ActiveOrigins []*loadbalance.Upstream
|
||||
InactiveOrigins []*loadbalance.Upstream
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(UpstreamCombinedList{
|
||||
ActiveOrigins: activeUpstreams,
|
||||
InactiveOrigins: inactiveUpstreams,
|
||||
})
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
// Add an upstream to a given proxy upstream endpoint
|
||||
func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
}
|
||||
|
||||
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not found")
|
||||
return
|
||||
}
|
||||
|
||||
upstreamOrigin, err := utils.PostPara(r, "origin")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "upstream origin not set")
|
||||
return
|
||||
}
|
||||
requireTLS, _ := utils.PostBool(r, "tls")
|
||||
skipTlsValidation, _ := utils.PostBool(r, "tlsval")
|
||||
bpwsorg, _ := utils.PostBool(r, "bpwsorg")
|
||||
preactivate, _ := utils.PostBool(r, "active")
|
||||
|
||||
//Create a new upstream object
|
||||
newUpstream := loadbalance.Upstream{
|
||||
OriginIpOrDomain: upstreamOrigin,
|
||||
RequireTLS: requireTLS,
|
||||
SkipCertValidations: skipTlsValidation,
|
||||
SkipWebSocketOriginCheck: bpwsorg,
|
||||
Weight: 1,
|
||||
MaxConn: 0,
|
||||
}
|
||||
|
||||
//Add the new upstream to endpoint
|
||||
err = targetEndpoint.AddUpstreamOrigin(&newUpstream, preactivate)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Save changes to configs
|
||||
err = SaveReverseProxyConfig(targetEndpoint)
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("INFO", "Unable to save new upstream to proxy config", err)
|
||||
utils.SendErrorResponse(w, "Failed to save new upstream config")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Update the connection configuration of this origin
|
||||
// pass in the whole new upstream origin json via "payload" POST variable
|
||||
// for missing fields, original value will be used instead
|
||||
func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
}
|
||||
|
||||
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not found")
|
||||
return
|
||||
}
|
||||
|
||||
//Editing upstream origin IP
|
||||
originIP, err := utils.PostPara(r, "origin")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||
return
|
||||
}
|
||||
originIP = strings.TrimSpace(originIP)
|
||||
|
||||
//Update content payload
|
||||
payload, err := utils.PostPara(r, "payload")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "update payload not set")
|
||||
return
|
||||
}
|
||||
|
||||
isActive, _ := utils.PostBool(r, "active")
|
||||
|
||||
targetUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Deep copy the upstream so other request handling goroutine won't be effected
|
||||
newUpstream := targetUpstream.Clone()
|
||||
|
||||
//Overwrite the new value into the old upstream
|
||||
err = json.Unmarshal([]byte(payload), &newUpstream)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Replace the old upstream with the new one
|
||||
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
err = targetEndpoint.AddUpstreamOrigin(newUpstream, isActive)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Save changes to configs
|
||||
err = SaveReverseProxyConfig(targetEndpoint)
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("INFO", "Unable to save upstream update to proxy config", err)
|
||||
utils.SendErrorResponse(w, "Failed to save updated upstream config")
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func ReverseProxyUpstreamSetPriority(w http.ResponseWriter, r *http.Request) {
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
}
|
||||
|
||||
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not found")
|
||||
return
|
||||
}
|
||||
|
||||
weight, err := utils.PostInt(r, "weight")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "priority not defined")
|
||||
return
|
||||
}
|
||||
|
||||
if weight < 0 {
|
||||
utils.SendErrorResponse(w, "invalid weight given")
|
||||
return
|
||||
}
|
||||
|
||||
//Editing upstream origin IP
|
||||
originIP, err := utils.PostPara(r, "origin")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||
return
|
||||
}
|
||||
originIP = strings.TrimSpace(originIP)
|
||||
|
||||
editingUpstream, err := targetEndpoint.GetUpstreamOriginByMatchingIP(originIP)
|
||||
editingUpstream.Weight = weight
|
||||
// The editing upstream is a pointer to the runtime object
|
||||
// and the change of weight do not requre a respawn of the proxy object
|
||||
// so no need to remove & re-prepare the upstream on weight changes
|
||||
|
||||
err = SaveReverseProxyConfig(targetEndpoint)
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("INFO", "Unable to update upstream weight", err)
|
||||
utils.SendErrorResponse(w, "Failed to update upstream weight")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) {
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
}
|
||||
|
||||
targetEndpoint, err := dynamicProxyRouter.LoadProxy(endpoint)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not found")
|
||||
return
|
||||
}
|
||||
|
||||
//Editing upstream origin IP
|
||||
originIP, err := utils.PostPara(r, "origin")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "origin ip or matching address not set")
|
||||
return
|
||||
}
|
||||
originIP = strings.TrimSpace(originIP)
|
||||
|
||||
if !targetEndpoint.UpstreamOriginExists(originIP) {
|
||||
utils.SendErrorResponse(w, "target upstream not found")
|
||||
return
|
||||
}
|
||||
|
||||
err = targetEndpoint.RemoveUpstreamOrigin(originIP)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
//Save changes to configs
|
||||
err = SaveReverseProxyConfig(targetEndpoint)
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("INFO", "Unable to remove upstream", err)
|
||||
utils.SendErrorResponse(w, "Failed to remove upstream from proxy rule")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
@ -28,7 +28,7 @@ func ReverseProxyListVdir(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var targetEndpoint *dynamicproxy.ProxyEndpoint
|
||||
if eptype == "host" {
|
||||
endpoint, err := utils.PostPara(r, "ep") //Support root and host
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
|
@ -51,13 +51,29 @@
|
||||
//Sort by RootOrMatchingDomain field
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||
data.forEach(subd => {
|
||||
let tlsIcon = "";
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
if (subd.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (subd.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
|
||||
//Build the upstream list
|
||||
let upstreams = "";
|
||||
if (subd.ActiveOrigins.length == 0){
|
||||
//Invalid config
|
||||
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`;
|
||||
}else{
|
||||
subd.ActiveOrigins.forEach(upstream => {
|
||||
console.log(upstream);
|
||||
//Check if the upstreams require TLS connections
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||
|
||||
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||
})
|
||||
}
|
||||
|
||||
let inboundTlsIcon = "";
|
||||
@ -102,7 +118,11 @@
|
||||
${aliasDomains}
|
||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
||||
<td data-label="" editable="true" datatype="domain">
|
||||
<div class="upstreamList">
|
||||
${upstreams}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
||||
@ -228,39 +248,21 @@
|
||||
var input;
|
||||
var datatype = $(this).attr("datatype");
|
||||
if (datatype == "domain"){
|
||||
let domain = payload.Domain;
|
||||
//Target require TLS for proxying
|
||||
let tls = payload.RequireTLS;
|
||||
if (tls){
|
||||
tls = "checked";
|
||||
}else{
|
||||
tls = "";
|
||||
let useStickySessionChecked = "";
|
||||
if (payload.UseStickySession){
|
||||
useStickySessionChecked = "checked";
|
||||
}
|
||||
input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
|
||||
<label>Use Sticky Session<br>
|
||||
<small>Enable stick session on load balancing</small></label>
|
||||
</div>
|
||||
|
||||
//Require TLS validation
|
||||
let skipTLSValidation = payload.SkipCertValidations;
|
||||
let checkstate = "";
|
||||
if (skipTLSValidation){
|
||||
checkstate = "checked";
|
||||
}
|
||||
|
||||
input = `
|
||||
<div class="ui mini fluid input">
|
||||
<input type="text" class="Domain" onchange="cleanProxyTargetValue(this)" value="${domain}">
|
||||
</div>
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" class="RequireTLS" ${tls}>
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div><br>
|
||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="purple server icon"></i> Load Balance</button> -->
|
||||
`;
|
||||
column.empty().append(input);
|
||||
column.append(input);
|
||||
$(column).find(".upstreamList").addClass("editing");
|
||||
}else if (datatype == "vdir"){
|
||||
//Append a quick access button for vdir page
|
||||
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
||||
@ -311,12 +313,6 @@
|
||||
Security Options
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipWebSocketOriginCheck" ${wsCheckstate}>
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||
<label>Require Rate Limit<br>
|
||||
@ -399,15 +395,11 @@
|
||||
}
|
||||
|
||||
var epttype = "host";
|
||||
let newDomain = $(row).find(".Domain").val();
|
||||
let requireTLS = $(row).find(".RequireTLS")[0].checked;
|
||||
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
|
||||
let useStickySession = $(row).find(".UseStickySession")[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)
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/edit",
|
||||
@ -415,11 +407,8 @@
|
||||
data: {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ep":newDomain,
|
||||
"ss":useStickySession,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"tls" :requireTLS,
|
||||
"tlsval": skipCertValidations,
|
||||
"bpwsorg" : bypassWebsocketOrigin,
|
||||
"bauth" :requireBasicAuth,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
@ -434,21 +423,6 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function cleanProxyTargetValue(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set checked");
|
||||
}
|
||||
}
|
||||
|
||||
/* button events */
|
||||
function editBasicAuthCredentials(uuid){
|
||||
@ -490,12 +464,12 @@
|
||||
}
|
||||
|
||||
//Open the load balance option
|
||||
function editLoadBalanceOptions(uuid){
|
||||
function editUpstreams(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
|
||||
showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function handleProxyRuleToggle(object){
|
||||
|
@ -122,7 +122,7 @@
|
||||
function initRootInfo(callback=undefined){
|
||||
$.get("/api/proxy/list?type=root", function(data){
|
||||
if (data == null){
|
||||
|
||||
msgbox("Default site load failed", false);
|
||||
}else{
|
||||
var $radios = $('input:radio[name=defaultsiteOption]');
|
||||
let proxyType = data.DefaultSiteOption;
|
||||
@ -140,8 +140,8 @@
|
||||
}
|
||||
updateAvaibleDefaultSiteOptions();
|
||||
|
||||
$("#proxyRoot").val(data.Domain);
|
||||
checkRootRequireTLS(data.Domain);
|
||||
$("#proxyRoot").val(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||
checkRootRequireTLS(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||
}
|
||||
|
||||
if (callback != undefined){
|
||||
@ -247,7 +247,9 @@
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
//OK
|
||||
|
||||
initRootInfo(function(){
|
||||
|
||||
//Check if WebServ is enabled
|
||||
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
|
||||
if (isUsingWebServ){
|
||||
@ -256,11 +258,7 @@
|
||||
setWebServerRunningState(true);
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
//Update the checkbox
|
||||
msgbox("Default Site Updated");
|
||||
}, 100);
|
||||
|
||||
msgbox("Default Site Updated");
|
||||
})
|
||||
});
|
||||
|
||||
@ -269,6 +267,9 @@
|
||||
if (btn != undefined){
|
||||
$(btn).removeClass("disabled");
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
msgbox("Unknown error occured", false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -26,8 +26,8 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Target IP Address or Domain Name with port</label>
|
||||
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||
<input type="text" id="proxyDomain" onchange="autoFillTargetTLS(this);">
|
||||
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||
</div>
|
||||
<div class="field dockerOptimizations" style="display:none;">
|
||||
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
||||
@ -76,6 +76,12 @@
|
||||
<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="useStickySessionLB">
|
||||
<label>Sticky Session<br><small>Enable stick session on upstream load balancing</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="requireRateLimit">
|
||||
@ -259,7 +265,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function autoFillTargetTLS(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#reqTls").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#reqTls").parent().checkbox("set checked");
|
||||
}else{
|
||||
//No http or https was given. Sniff it
|
||||
autoCheckTls(targetDomain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Automatic check if the site require TLS and check the checkbox if needed
|
||||
function autoCheckTls(targetDomain){
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
@ -453,7 +478,25 @@
|
||||
}
|
||||
|
||||
/* UI Element Initialization */
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
function initAdvanceSettingsAccordion(){
|
||||
function hasClickEvent(element) {
|
||||
var events = $._data(element, "events");
|
||||
return events && events.click && events.click.length > 0;
|
||||
}
|
||||
|
||||
if (!hasClickEvent($("#advanceProxyRules"))){
|
||||
// Not sure why sometime the accordion events are not binding
|
||||
// to the DOM element. This makes sure the element is binded
|
||||
// correctly by checking it again after 300ms
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
setTimeout(function(){
|
||||
initAdvanceSettingsAccordion();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
initAdvanceSettingsAccordion();
|
||||
|
||||
|
||||
|
||||
</script>
|
@ -9,7 +9,6 @@
|
||||
<title>Control Panel | Zoraxy</title>
|
||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||
<script src="script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/ao_module.js"></script>
|
||||
<script src="script/semantic/semantic.min.js"></script>
|
||||
<script src="script/tablesort.js"></script>
|
||||
<script src="script/countryCode.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
@ -133,11 +134,17 @@
|
||||
</div>
|
||||
<div class="field dnsChallengeOnly" style="display:none;">
|
||||
<div class="ui divider"></div>
|
||||
<p>DNS Credentials (Leave all fields empty to use previous settings)<br>
|
||||
<small><i class="yellow exclamation triangle icon"></i> Note that domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</small></p>
|
||||
<p>DNS Credentials</p>
|
||||
<div id="dnsProviderAPIFields">
|
||||
|
||||
<p><i class="ui loading circle notch icon"></i> Generating WebForm</p>
|
||||
</div>
|
||||
<h4><i class="yellow exclamation triangle icon"></i> Notes & FAQ</h4>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</div>
|
||||
<div class="item">For some DNS providers like CloudFlare, you do not need to fill in all fields.</div>
|
||||
<div class="item">If you are not sure what to fill in, check out the documentation from <a href="https://go-acme.github.io/lego/dns/" target="_blank">lego (DNS challenge library)</a></div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<label>Credentials File Content</label>
|
||||
<textarea id="dnsCredentials" placeholder=""></textarea>
|
||||
@ -740,6 +747,9 @@
|
||||
function toggleDnsChallenge(){
|
||||
if ( $("#useDnsChallenge")[0].checked){
|
||||
$(".dnsChallengeOnly").show();
|
||||
setTimeout(function(){
|
||||
$("#dnsProvider").dropdown("set text", "Cloudflare");
|
||||
}, 500);
|
||||
}else{
|
||||
$(".dnsChallengeOnly").hide();
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@ -1,49 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Load Balance
|
||||
<div class="sub header epname"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br><br><br><br>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let aliasList = [];
|
||||
let editingEndpoint = {};
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
try{
|
||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||
$(".epname").text(payloadHash.ep);
|
||||
editingEndpoint = payloadHash;
|
||||
}catch(ex){
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
|
||||
function closeThisWrapper(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,6 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
528
src/web/snippet/upstreams.html
Normal file
528
src/web/snippet/upstreams.html
Normal file
@ -0,0 +1,528 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
<style>
|
||||
.upstreamActions{
|
||||
position: absolute;
|
||||
top: 0.6em;
|
||||
right: 0.6em;
|
||||
}
|
||||
|
||||
.upstreamLink{
|
||||
max-width: 220px;
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.upstreamEntry .ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
#activateNewUpstream.ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
#upstreamTable{
|
||||
max-height: 480px;
|
||||
border-radius: 0.3em;
|
||||
padding: 0.3em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.upstreamEntry.inactive{
|
||||
background-color: #f3f3f3 !important;
|
||||
}
|
||||
|
||||
.upstreamEntry{
|
||||
border-radius: 0.4em !important;
|
||||
border: 1px solid rgb(233, 233, 233) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 499px) {
|
||||
.upstreamActions{
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
margin-left: 0.4em;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Upstreams / Load Balance
|
||||
<div class="sub header epname"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui small pointing secondary menu">
|
||||
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
|
||||
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
|
||||
</div>
|
||||
<div class="ui tab basic segment active" data-tab="upstreamlist">
|
||||
<!-- A list of current existing upstream on this reverse proxy-->
|
||||
<div id="upstreamTable">
|
||||
<div class="ui segment">
|
||||
<a><i class="ui loading spinner icon"></i> Loading</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<i class="ui blue info circle icon"></i> Round-robin load balancing algorithm will be used for upstreams with same weight. Set weight to 0 for fallback only.
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui tab basic segment" data-tab="newupstream">
|
||||
<!-- Web Form to create a new upstream -->
|
||||
<h4 class="ui header">
|
||||
<i class="green add icon"></i>
|
||||
<div class="content">
|
||||
Add Upstream Server
|
||||
<div class="sub header">Create new load balance or fallback upstream origin</div>
|
||||
</div>
|
||||
</h4>
|
||||
<p style="margin-bottom: 0.4em;">Target IP address with port</p>
|
||||
<div class="ui fluid small input">
|
||||
<input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
|
||||
</div>
|
||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||
<br><br>
|
||||
<div id="activateNewUpstream" class="ui toggle checkbox" style="display:inline-block;">
|
||||
<input type="checkbox" id="activateNewUpstreamCheckbox" style="margin-top: 0.4em;" checked>
|
||||
<label>Activate<br>
|
||||
<small>Enable this upstream for load balancing</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 1.2em;">
|
||||
<input type="checkbox" id="requireTLS">
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" id="skipTlsVerification">
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" id="SkipWebSocketOriginCheck" checked>
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div>
|
||||
<br><br>
|
||||
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<br><br><br><br>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let origins = [];
|
||||
let editingEndpoint = {};
|
||||
$('.menu .item').tab();
|
||||
|
||||
|
||||
function initOriginList(){
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/list",
|
||||
method: "POST",
|
||||
data: {
|
||||
"type":"host",
|
||||
"ep": editingEndpoint.ep
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
//This endpoint not exists?
|
||||
alert(data.error);
|
||||
return;
|
||||
}else{
|
||||
$("#upstreamTable").html("");
|
||||
|
||||
if (data.ActiveOrigins.length == 0){
|
||||
//There is no upstream for this proxy rule
|
||||
$("#upstreamTable").append(`<tr>
|
||||
<td colspan="2"><b><i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin</b><br>
|
||||
This HTTP proxy rule will always return Error 521 when requested. To fix this, add or enable a upstream origin to this proxy endpoint.
|
||||
<div class="ui divider"></div>
|
||||
</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
data.ActiveOrigins.forEach(upstream => {
|
||||
renderUpstreamEntryToTable(upstream, true);
|
||||
});
|
||||
|
||||
data.InactiveOrigins.forEach(upstream => {
|
||||
renderUpstreamEntryToTable(upstream, false);
|
||||
});
|
||||
|
||||
let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
|
||||
if (totalUpstreams == 1){
|
||||
$(".lowPriorityButton").addClass('disabled');
|
||||
}
|
||||
|
||||
if (parent && parent.document.getElementById("httpProxyList") != null){
|
||||
//Also update the parent display
|
||||
let element = $(parent.document.getElementById("httpProxyList")).find(".upstreamList.editing");
|
||||
let upstreams = "";
|
||||
data.ActiveOrigins.forEach(upstream => {
|
||||
//Check if the upstreams require TLS connections
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||
|
||||
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||
});
|
||||
|
||||
if (data.ActiveOrigins.length == 0){
|
||||
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`
|
||||
}
|
||||
$(element).html(upstreams);
|
||||
}
|
||||
|
||||
$(".ui.checkbox").checkbox();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function renderUpstreamEntryToTable(upstream, isActive){
|
||||
function newUID(){return"00000000-0000-4000-8000-000000000000".replace(/0/g,function(){return(0|Math.random()*16).toString(16)})};
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
//Priority Arrows
|
||||
let downArrowClass = "";
|
||||
if (upstream.Weight == 0 ){
|
||||
//Cannot go any lower
|
||||
downArrowClass = "disabled";
|
||||
}
|
||||
let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
|
||||
let payload = encodeURIComponent(JSON.stringify(upstream));
|
||||
let domUID = newUID();
|
||||
$("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
|
||||
<h4 class="ui header">
|
||||
<div class="ui toggle checkbox" style="display:inline-block;">
|
||||
<input type="checkbox" class="enableState" name="enabled" style="margin-top: 0.4em;" onchange="saveUpstreamUpdate('${domUID}');" ${isActive?"checked":""}>
|
||||
<label></label>
|
||||
</div>
|
||||
<div class="content">
|
||||
<a href="${url}" target="_blank" class="upstreamLink">${upstream.OriginIpOrDomain} ${tlsIcon}</a>
|
||||
<div class="sub header">${isActive?(upstream.Weight==0?"Fallback Only":"Active"):"Inactive"} | Weight: ${upstream.Weight}x </div>
|
||||
</div>
|
||||
</h4>
|
||||
<div class="advanceOptions" style="display:none;">
|
||||
<div class="upstreamOriginField">
|
||||
<p>New upstream origin IP address or domain</p>
|
||||
<div class="ui small fluid input" style="margin-top: -0.6em;">
|
||||
<input type="text" class="newOrigin" value="${upstream.OriginIpOrDomain}" onchange="handleAutoOriginClean('${domUID}');">
|
||||
</div>
|
||||
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" class="reqTLSCheckbox" ${upstream.RequireTLS?"checked":""}>
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" class="skipVerificationCheckbox" ${upstream.SkipCertValidations?"checked":""}>
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipWebSocketOriginCheck" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div><br>
|
||||
</div>
|
||||
<div class="upstreamActions">
|
||||
<!-- Change Priority -->
|
||||
<button class="ui basic circular icon button highPriorityButton" title="Increase Weight" onclick="increaseUpstreamWeight('${domUID}');"><i class="ui arrow up icon"></i></button>
|
||||
<button class="ui basic circular icon button lowPriorityButton ${downArrowClass}" title="Reduce Weight" onclick="decreaseUpstreamWeight('${domUID}');"><i class="ui arrow down icon"></i></button>
|
||||
<button class="ui basic circular icon editbtn button" onclick="handleUpstreamOriginEdit('${domUID}');" title="Edit Upstream Destination"><i class="ui grey edit icon"></i></button>
|
||||
<button style="display:none;" class="ui basic circular icon trashbtn button" onclick="removeUpstream('${domUID}');" title="Remove Upstream"><i class="ui red trash icon"></i></button>
|
||||
<button style="display:none;" class="ui basic circular icon savebtn button" onclick="saveUpstreamUpdate('${domUID}');" title="Save Changes"><i class="ui green save icon"></i></button>
|
||||
<button style="display:none;" class="ui basic circular icon cancelbtn button" onclick="initOriginList();" title="Cancel"><i class="ui grey times icon"></i></button>
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
/* New Upstream Origin Functions */
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function cleanProxyTargetValue(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#requireTLS").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#requireTLS").parent().checkbox("set checked");
|
||||
}else{
|
||||
//URL does not contains https or http protocol tag
|
||||
//sniff header
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
$("#requireTLS").parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$("#requireTLS").parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//Add a new upstream to this http proxy rule
|
||||
function addNewUpstream(){
|
||||
let origin = $("#originURL").val().trim();
|
||||
let requireTLS = $("#requireTLS")[0].checked;
|
||||
let skipVerification = $("#skipTlsVerification")[0].checked;
|
||||
let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
|
||||
let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
|
||||
|
||||
if (origin == ""){
|
||||
parent.msgbox("Upstream origin cannot be empty", false);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/add",
|
||||
method: "POST",
|
||||
data:{
|
||||
"ep": editingEndpoint.ep,
|
||||
"origin": origin,
|
||||
"tls": requireTLS,
|
||||
"tlsval": skipVerification,
|
||||
"bpwsorg":skipWebSocketOriginCheck,
|
||||
"active": activateLoadbalancer,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("New upstream origin added");
|
||||
initOriginList();
|
||||
$("#originURL").val("");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Get a upstream setting data from DOM element
|
||||
function getUpstreamSettingFromDOM(upstream){
|
||||
//Get the original setting from DOM payload
|
||||
let originalSettings = $(upstream).attr("data-payload");
|
||||
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||
|
||||
//Get the updated settings if any
|
||||
let requireTLS = $(upstream).find(".reqTLSCheckbox")[0].checked;
|
||||
let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
|
||||
let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
|
||||
|
||||
//Update the original setting with new one just applied
|
||||
originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
|
||||
originalSettings.RequireTLS = requireTLS;
|
||||
originalSettings.SkipCertValidations = skipTLSVerification;
|
||||
originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
|
||||
|
||||
//console.log(originalSettings);
|
||||
return originalSettings;
|
||||
}
|
||||
|
||||
//Handle setting change on upstream config
|
||||
function saveUpstreamUpdate(upstreamDomID){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||
let originalSettings = $(targetDOM).attr("data-payload");
|
||||
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||
let newConfig = getUpstreamSettingFromDOM(targetDOM);
|
||||
let isActive = $(targetDOM).find(".enableState")[0].checked;
|
||||
console.log(newConfig);
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/update",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||
payload: JSON.stringify(newConfig),
|
||||
active: isActive,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
|
||||
}else{
|
||||
parent.msgbox("Upstream setting updated");
|
||||
initOriginList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Edit the upstream origin of this upstream entry
|
||||
function handleUpstreamOriginEdit(upstreamDomID){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||
let originalSettings = getUpstreamSettingsFromDomID(upstreamDomID);
|
||||
let originIP = originalSettings.OriginIpOrDomain;
|
||||
|
||||
//Change the UI to edit mode
|
||||
$(".editbtn").hide();
|
||||
$(".lowPriorityButton").hide();
|
||||
$(".highPriorityButton").hide();
|
||||
$(targetDOM).find(".trashbtn").show();
|
||||
$(targetDOM).find(".savebtn").show();
|
||||
$(targetDOM).find(".cancelbtn").show();
|
||||
$(targetDOM).find(".advanceOptions").show();
|
||||
}
|
||||
|
||||
//Check if the entered URL contains http or https
|
||||
function handleAutoOriginClean(domid){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||
let targetTLSCheckbox = $(targetDOM).find(".reqTLSCheckbox");
|
||||
let targetDomain = $(targetDOM).find(".newOrigin").val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||
}else{
|
||||
//URL does not contains https or http protocol tag
|
||||
//sniff header
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getUpstreamSettingsFromDomID(domid){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||
if (targetDOM.length == 0){
|
||||
return undefined;
|
||||
}
|
||||
let upstreamSettings = $(targetDOM).attr("data-payload");
|
||||
upstreamSettings = JSON.parse(decodeURIComponent(upstreamSettings));
|
||||
return upstreamSettings;
|
||||
}
|
||||
|
||||
function increaseUpstreamWeight(domid){
|
||||
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||
let currentWeight = upstreamSetting.Weight;
|
||||
setUpstreamWeight(originIP, currentWeight+1);
|
||||
}
|
||||
|
||||
function decreaseUpstreamWeight(domid){
|
||||
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||
let currentWeight = upstreamSetting.Weight;
|
||||
setUpstreamWeight(originIP, currentWeight-1);
|
||||
}
|
||||
|
||||
//Set a weight of a upstream
|
||||
function setUpstreamWeight(originIP, newWeight){
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/setPriority",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
origin: originIP,
|
||||
weight: newWeight,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Upstream Weight Updated");
|
||||
initOriginList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Handle removal of an upstream
|
||||
function removeUpstream(domid){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||
let originalSettings = $(targetDOM).attr("data-payload");
|
||||
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||
let UpstreamKey = originalSettings.OriginIpOrDomain;
|
||||
if (!confirm("Confirm removing " + UpstreamKey + " from upstream?")){
|
||||
return;
|
||||
}
|
||||
//Remove the upstream
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/remove",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
|
||||
}else{
|
||||
parent.msgbox("Upstream deleted");
|
||||
initOriginList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
try{
|
||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||
$(".epname").text(payloadHash.ep);
|
||||
editingEndpoint = payloadHash;
|
||||
initOriginList();
|
||||
}catch(ex){
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
|
||||
function closeThisWrapper(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -25,6 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/ipscan"
|
||||
"imuslab.com/zoraxy/mod/mdns"
|
||||
"imuslab.com/zoraxy/mod/uptime"
|
||||
@ -124,37 +125,42 @@ func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Ta
|
||||
|
||||
UptimeTargets := []*uptime.Target{}
|
||||
for hostid, target := range hosts {
|
||||
url := "http://" + target.Domain
|
||||
protocol := "http"
|
||||
if target.RequireTLS {
|
||||
url = "https://" + target.Domain
|
||||
protocol = "https"
|
||||
}
|
||||
for _, origin := range target.ActiveOrigins {
|
||||
|
||||
//Add the root url
|
||||
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
||||
ID: hostid,
|
||||
Name: hostid,
|
||||
URL: url,
|
||||
Protocol: protocol,
|
||||
})
|
||||
|
||||
//Add each virtual directory into the list
|
||||
for _, vdir := range target.VirtualDirectories {
|
||||
url := "http://" + vdir.Domain
|
||||
url := "http://" + origin.OriginIpOrDomain
|
||||
protocol := "http"
|
||||
if target.RequireTLS {
|
||||
url = "https://" + vdir.Domain
|
||||
if origin.RequireTLS {
|
||||
url = "https://" + origin.OriginIpOrDomain
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
//Add the root url
|
||||
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
||||
ID: hostid + vdir.MatchingPath,
|
||||
Name: hostid + vdir.MatchingPath,
|
||||
URL: url,
|
||||
Protocol: protocol,
|
||||
ID: hostid,
|
||||
Name: hostid,
|
||||
URL: url,
|
||||
Protocol: protocol,
|
||||
ProxyType: uptime.ProxyType_Host,
|
||||
})
|
||||
|
||||
//Add each virtual directory into the list
|
||||
for _, vdir := range target.VirtualDirectories {
|
||||
url := "http://" + vdir.Domain
|
||||
protocol := "http"
|
||||
if origin.RequireTLS {
|
||||
url = "https://" + vdir.Domain
|
||||
protocol = "https"
|
||||
}
|
||||
//Add the root url
|
||||
UptimeTargets = append(UptimeTargets, &uptime.Target{
|
||||
ID: hostid + vdir.MatchingPath,
|
||||
Name: hostid + vdir.MatchingPath,
|
||||
URL: url,
|
||||
Protocol: protocol,
|
||||
ProxyType: uptime.ProxyType_Vdir,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,7 +193,16 @@ func HandleStaticWebServerPortChange(w http.ResponseWriter, r *http.Request) {
|
||||
if dynamicProxyRouter.Root.DefaultSiteOption == dynamicproxy.DefaultSite_InternalStaticWebServer {
|
||||
//Update the root site as well
|
||||
newDraftingRoot := dynamicProxyRouter.Root.Clone()
|
||||
newDraftingRoot.Domain = "127.0.0.1:" + strconv.Itoa(newPort)
|
||||
|
||||
newDraftingRoot.ActiveOrigins = []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: "127.0.0.1:" + strconv.Itoa(newPort),
|
||||
RequireTLS: false,
|
||||
SkipCertValidations: false,
|
||||
SkipWebSocketOriginCheck: true,
|
||||
Weight: 0,
|
||||
},
|
||||
}
|
||||
activatedNewRoot, err := dynamicProxyRouter.PrepareProxyRoute(newDraftingRoot)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to update root routing rule")
|
||||
|
Loading…
x
Reference in New Issue
Block a user