Browse Source

Added wip load balancing func

Toby Chui 8 months ago
parent
commit
89cb3c488c

+ 2 - 0
api.go

@@ -61,6 +61,8 @@ 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/add", ReverseProxyUpstreamAdd)
 	//Reverse proxy virtual directory APIs
 	authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir)
 	authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir)

+ 1 - 1
mod/dynamicproxy/dpcore/dpcore.go

@@ -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

+ 25 - 0
mod/dynamicproxy/endpoints.go

@@ -7,6 +7,7 @@ import (
 
 	"golang.org/x/text/cases"
 	"golang.org/x/text/language"
+	"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
 )
 
 /*
@@ -133,6 +134,30 @@ 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.Origins {
+		if origin.OriginIpOrDomain == originURL {
+			return true
+		}
+	}
+	return false
+}
+
+// Add upstream to origin and update it to runtime
+func (ep *ProxyEndpoint) AddUpstreamOrigin(newOrigin *loadbalance.Upstream) error {
+	//Check if the upstream already exists
+	if ep.UpstreamOriginExists(newOrigin.OriginIpOrDomain) {
+		return errors.New("upstream with same origin already exists")
+	}
+
+	//Ok, add the origin to list
+	ep.Origins = append(ep.Origins, newOrigin)
+	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

+ 2 - 0
mod/dynamicproxy/loadbalance/originPicker.go

@@ -2,6 +2,7 @@ package loadbalance
 
 import (
 	"errors"
+	"fmt"
 	"net/http"
 )
 
@@ -20,5 +21,6 @@ func (m *RouteManager) GetRequestUpstreamTarget(r *http.Request, origins []*Upst
 	}
 
 	//TODO: Add upstream picking algorithm here
+	fmt.Println("DEBUG: Picking origin " + origins[0].OriginIpOrDomain)
 	return origins[0], nil
 }

+ 75 - 0
upstreams.go

@@ -0,0 +1,75 @@
+package main
+
+import (
+	"net/http"
+
+	"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
+	"imuslab.com/zoraxy/mod/utils"
+)
+
+/*
+	Upstreams.go
+
+	This script handle upstream and load balancer
+	related API
+*/
+
+// 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")
+
+	//Create a new upstream object
+	newUpstream := loadbalance.Upstream{
+		OriginIpOrDomain:         upstreamOrigin,
+		RequireTLS:               requireTLS,
+		SkipCertValidations:      skipTlsValidation,
+		SkipWebSocketOriginCheck: bpwsorg,
+		Priority:                 0,
+		MaxConn:                  0,
+		Disabled:                 false,
+	}
+
+	//Add the new upstream to endpoint
+	err = targetEndpoint.AddUpstreamOrigin(&newUpstream)
+	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)
+}
+
+func ReverseProxyUpstreamUpdate(w http.ResponseWriter, r *http.Request) {
+
+}
+
+func ReverseProxyUpstreamDelete(w http.ResponseWriter, r *http.Request) {
+
+}

+ 1 - 1
vdir.go

@@ -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

+ 1 - 0
web/snippet/accessRuleEditor.html

@@ -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 - 0
web/snippet/acme.html

@@ -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 - 0
web/snippet/advanceStatsOprs.html

@@ -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 - 0
web/snippet/aliasEditor.html

@@ -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 - 0
web/snippet/basicAuthEditor.html

@@ -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 - 0
web/snippet/configTools.html

@@ -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 - 0
web/snippet/customHeaders.html

@@ -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 - 0
web/snippet/dockerContainersList.html

@@ -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 - 0
web/snippet/hostAccessEditor.html

@@ -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 - 0
web/snippet/intermediateCertConv.html

@@ -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 - 0
web/snippet/placeholder.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>

+ 55 - 5
web/snippet/upstreams.html

@@ -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>
@@ -23,7 +24,7 @@
             }
 
             #upstreamTable{
-                max-height: 400px;
+                max-height: 480px;
                 border-radius: 0.3em;
                 padding: 0.3em;
                 overflow-y: auto;
@@ -70,12 +71,12 @@
                     <i class="green add icon"></i>
                     <div class="content">
                         Add Upstream Server
-                        <div class="sub header">Create new fallback or load balance upstream</div>
+                        <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" placeholder="" onchange="cleanProxyTargetValue(this);"><br>
+                    <input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
                 </div>
                 <small>E.g. 192.168.0.101:8000 or example.com</small>
                 <br><br>
@@ -90,12 +91,12 @@
                         <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" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
+                    <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"><i class="ui green circle check icon"></i> Create</button>
+                <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" >
@@ -218,9 +219,58 @@
                     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;
+
+                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
+                    },
+                    success: function(data){
+                        if (data.error != undefined){
+                            parent.msgbox(data.error, false);
+                        }else{
+                            parent.msgbox("New upstream origin added");
+                            initOriginList();
+                        }
+                    }
+                })
+            }
+
             if (window.location.hash.length > 1){
                 let payloadHash = window.location.hash.substr(1);
                 try{