Toby Chui 9 месяцев назад
Родитель
Сommit
94131a0d36

+ 12 - 0
mod/dynamicproxy/customHeader.go

@@ -0,0 +1,12 @@
+package dynamicproxy
+
+/*
+	CustomHeader.go
+
+	This script handle parsing and injecting custom headers
+	into the dpcore routing logic
+*/
+
+func (ept *ProxyEndpoint) GenerateProxyHeaderArrays() {
+
+}

+ 7 - 5
mod/dynamicproxy/dpcore/dpcore.go

@@ -57,11 +57,13 @@ type ReverseProxy struct {
 }
 
 type ResponseRewriteRuleSet struct {
-	ProxyDomain  string
-	OriginalHost string
-	UseTLS       bool
-	NoCache      bool
-	PathPrefix   string //Vdir prefix for root, / will be rewrite to this
+	ProxyDomain       string
+	OriginalHost      string
+	UseTLS            bool
+	NoCache           bool
+	PathPrefix        string //Vdir prefix for root, / will be rewrite to this
+	UpstreamHeaders   [][]string
+	DownstreamHeaders [][]string
 }
 
 type requestCanceler interface {

+ 5 - 9
mod/dynamicproxy/endpoints.go

@@ -30,7 +30,6 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
 			return true
 		}
 	}
-
 	return false
 }
 
@@ -49,16 +48,13 @@ func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
 }
 
 // Add a user defined header to the list, duplicates will be automatically removed
-func (ep *ProxyEndpoint) AddUserDefinedHeader(key string, value string) error {
-	if ep.UserDefinedHeaderExists(key) {
-		ep.RemoveUserDefinedHeader(key)
+func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *UserDefinedHeader) error {
+	if ep.UserDefinedHeaderExists(newHeaderRule.Key) {
+		ep.RemoveUserDefinedHeader(newHeaderRule.Key)
 	}
 
-	ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, &UserDefinedHeader{
-		Key:   cases.Title(language.Und, cases.NoLower).String(key), //e.g. x-proxy-by -> X-Proxy-By
-		Value: value,
-	})
-
+	newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
+	ep.UserDefinedHeaders = append(ep.UserDefinedHeaders, newHeaderRule)
 	return nil
 }
 

+ 7 - 7
mod/dynamicproxy/proxyRequestHandler.go

@@ -111,13 +111,6 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
 	r.Header.Set("X-Forwarded-Host", r.Host)
 	r.Header.Set("X-Forwarded-Server", "zoraxy-"+h.Parent.Option.HostUUID)
 
-	//Inject custom headers
-	if len(target.UserDefinedHeaders) > 0 {
-		for _, customHeader := range target.UserDefinedHeaders {
-			r.Header.Set(customHeader.Key, customHeader.Value)
-		}
-	}
-
 	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
@@ -152,6 +145,13 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
 		r.URL, _ = url.Parse(originalHostHeader)
 	}
 
+	//Build downstream and upstream header rules
+	if len(target.UserDefinedHeaders) > 0 {
+		for _, customHeader := range target.UserDefinedHeaders {
+			r.Header.Set(customHeader.Key, customHeader.Value)
+		}
+	}
+
 	err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
 		ProxyDomain:  target.Domain,
 		OriginalHost: originalHostHeader,

+ 49 - 31
reverseproxy.go

@@ -1076,9 +1076,9 @@ func HandleCustomHeaderList(w http.ResponseWriter, r *http.Request) {
 
 // Add a new header to the target endpoint
 func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
-	epType, err := utils.PostPara(r, "type")
+	rewriteType, err := utils.PostPara(r, "type")
 	if err != nil {
-		utils.SendErrorResponse(w, "endpoint type not defined")
+		utils.SendErrorResponse(w, "rewriteType not defined")
 		return
 	}
 
@@ -1088,6 +1088,12 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	direction, err := utils.PostPara(r, "direction")
+	if err != nil {
+		utils.SendErrorResponse(w, "HTTP modifiy direction not set")
+		return
+	}
+
 	name, err := utils.PostPara(r, "name")
 	if err != nil {
 		utils.SendErrorResponse(w, "HTTP header name not set")
@@ -1095,26 +1101,47 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
 	}
 
 	value, err := utils.PostPara(r, "value")
-	if err != nil {
+	if err != nil && rewriteType == "add" {
 		utils.SendErrorResponse(w, "HTTP header value not set")
 		return
 	}
 
-	var targetProxyEndpoint *dynamicproxy.ProxyEndpoint
-	if epType == "root" {
-		targetProxyEndpoint = dynamicProxyRouter.Root
+	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
+	if err != nil {
+		utils.SendErrorResponse(w, "target endpoint not exists")
+		return
+	}
+
+	//Create a Custom Header Defination type
+	var rewriteDirection dynamicproxy.HeaderDirection
+	if direction == "toOrigin" {
+		rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToUpstream
+	} else if direction == "toClient" {
+		rewriteDirection = dynamicproxy.HeaderDirection_ZoraxyToDownstream
 	} else {
-		ep, err := dynamicProxyRouter.LoadProxy(domain)
-		if err != nil {
-			utils.SendErrorResponse(w, "target endpoint not exists")
-			return
-		}
+		//Unknown direction
+		utils.SendErrorResponse(w, "header rewrite direction not supported")
+		return
+	}
 
-		targetProxyEndpoint = ep
+	isRemove := false
+	if rewriteType == "remove" {
+		isRemove = true
+	}
+	headerRewriteDefination := dynamicproxy.UserDefinedHeader{
+		Key:       name,
+		Value:     value,
+		Direction: rewriteDirection,
+		IsRemove:  isRemove,
+		IsAppend:  false,
 	}
 
 	//Create a new custom header object
-	targetProxyEndpoint.AddUserDefinedHeader(name, value)
+	err = targetProxyEndpoint.AddUserDefinedHeader(&headerRewriteDefination)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to add header rewrite rule: "+err.Error())
+		return
+	}
 
 	//Save it (no need reload as header are not handled by dpcore)
 	err = SaveReverseProxyConfig(targetProxyEndpoint)
@@ -1128,12 +1155,6 @@ func HandleCustomHeaderAdd(w http.ResponseWriter, r *http.Request) {
 
 // Remove a header from the target endpoint
 func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
-	epType, err := utils.PostPara(r, "type")
-	if err != nil {
-		utils.SendErrorResponse(w, "endpoint type not defined")
-		return
-	}
-
 	domain, err := utils.PostPara(r, "domain")
 	if err != nil {
 		utils.SendErrorResponse(w, "domain or matching rule not defined")
@@ -1146,20 +1167,17 @@ func HandleCustomHeaderRemove(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	var targetProxyEndpoint *dynamicproxy.ProxyEndpoint
-	if epType == "root" {
-		targetProxyEndpoint = dynamicProxyRouter.Root
-	} else {
-		ep, err := dynamicProxyRouter.LoadProxy(domain)
-		if err != nil {
-			utils.SendErrorResponse(w, "target endpoint not exists")
-			return
-		}
-
-		targetProxyEndpoint = ep
+	targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
+	if err != nil {
+		utils.SendErrorResponse(w, "target endpoint not exists")
+		return
 	}
 
-	targetProxyEndpoint.RemoveUserDefinedHeader(name)
+	err = targetProxyEndpoint.RemoveUserDefinedHeader(name)
+	if err != nil {
+		utils.SendErrorResponse(w, "unable to remove header rewrite rule: "+err.Error())
+		return
+	}
 
 	err = SaveReverseProxyConfig(targetProxyEndpoint)
 	if err != nil {

+ 20 - 4
web/components/status.html

@@ -68,12 +68,13 @@
 <div class="standardContainer">
     <div class="ui divider"></div>
     <h4>Global Settings</h4>
-    <p>Inbound Port (Port to be proxied)</p>
+    <p>Inbound Port (Reverse Proxy Listening Port)</p>
     <div class="ui action fluid notloopbackOnly input">
+        <small id="applyButtonReminder">Click "Apply" button to confirm listening port changes</small>
         <input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
-        <button class="ui basic notloopbackOnly button" onclick="handlePortChange();"><i class="ui green checkmark icon"></i> Apply</button>
+        <button class="ui green notloopbackOnly button" style="background: linear-gradient(60deg, #27e7ff, #00ca52);" onclick="handlePortChange();"><i class="ui checkmark icon"></i> Apply</button>
     </div>
-    <br>
+    <br><br>
     <div id="tls" class="ui toggle notloopbackOnly checkbox">
         <input type="checkbox">
         <label>Use TLS to serve proxy request</label>
@@ -160,6 +161,7 @@
 </div>
 <script>
     let loopbackProxiedInterface = false;
+    let currentListeningPort = 80;
     $(".advanceSettings").accordion();
 
     //Initial the start stop button if this is reverse proxied
@@ -176,6 +178,8 @@
     //Get the latest server status from proxy server
     function initRPStaste(){
         $.get("/api/proxy/status", function(data){
+            $("#incomingPort").off("change");
+
             if (data.Running == true){
                 $("#startbtn").addClass("disabled");
                 if (!loopbackProxiedInterface){
@@ -194,6 +198,15 @@
                 $("#serverstatus").removeClass("green");
             }
             $("#incomingPort").val(data.Option.Port);
+            currentListeningPort = data.Option.Port;
+            $("#incomingPort").on("change", function(){
+                let newPortValue = $("#incomingPort").val().trim();
+                if (currentListeningPort != newPortValue){
+                    $("#applyButtonReminder").show();
+                }else{
+                    $("#applyButtonReminder").hide();
+                }
+            });
             
         });
         
@@ -353,8 +366,11 @@
                 msgbox(data.error, false, 5000);
                 return;
             }
-            msgbox("Setting Updated");
+            msgbox("Listening Port Updated");
             initRPStaste();
+
+            //Hide the reminder text
+            $("#applyButtonReminder").hide();
         });
     }
 

+ 10 - 0
web/main.css

@@ -509,6 +509,16 @@ body{
     }
   }
 
+  /* Remind message for user forgetting to click Apply button*/
+  #applyButtonReminder{
+    position: absolute; 
+    bottom:-1.6em; 
+    left: 0; 
+    font-weight: bolder; 
+    color: #faac26; 
+    display:none;
+  }
+
 /*
   HTTP Proxy & Virtual Directory
 */

+ 75 - 14
web/snippet/customHeaders.html

@@ -23,7 +23,7 @@
             <table class="ui very basic compacted unstackable celled table">
                 <thead>
                 <tr>
-                    <th>Name</th>
+                    <th>Key</th>
                     <th>Value</th>
                     <th>Remove</th>
                 </tr></thead>
@@ -34,18 +34,29 @@
                 </tbody>
             </table>
             <div class="ui divider"></div>
-            <h4>Add Custom Header</h4>
-            <p>Add custom header(s) into this proxy target</p>
+            <h4>Edit Custom Header</h4>
+            <p>Add or remove custom header(s) over this proxy target</p>
             <div class="scrolling content ui form">
-                <div class="three small fields credentialEntry">
+                <div class="five small fields credentialEntry">
+                    <div class="field" align="center">
+                        <button id="toOriginButton" title="Downstream to Upstream" class="ui circular basic active button">Zoraxy <i class="angle double right blue icon" style="margin-right: 0.4em;"></i> Origin</button>
+                        <button id="toClientButton" title="Upstream to Downstream" class="ui circular basic button">Client <i class="angle double left orange icon" style="margin-left: 0.4em;"></i> Zoraxy</button>
+                    </div>
+                    <div class="field" align="center">
+                        <button id="headerModeAdd" class="ui circular basic active button"><i class="ui green circle add icon"></i> Add Header</button>
+                        <button id="headerModeRemove" class="ui circular basic button"><i class="ui red circle times icon"></i> Remove Header</button>
+                    </div>
                     <div class="field">
+                        <label>Header Key</label>
                         <input id="headerName" type="text" placeholder="X-Custom-Header" autocomplete="off">
+                        <small>The header key is <b>NOT</b> case sensitive</small>
                     </div>
                     <div class="field">
+                        <label>Header Value</label>
                         <input id="headerValue" type="text" placeholder="value1,value2,value3" autocomplete="off">
                     </div>
                     <div class="field" >
-                        <button class="ui basic button" onclick="addCustomHeader();"><i class="green add icon"></i> Add Header</button>
+                        <button class="ui basic button" onclick="addCustomHeader();"><i class="green add icon"></i> Add Header Rewrite Rule</button>
                     </div>
                     <div class="ui divider"></div>
                 </div>
@@ -75,6 +86,47 @@
                 parent.hideSideWrapper(true);
             }
 
+            //Bind events to header mod mode
+            $("#headerModeAdd").on("click", function(){
+                $("#headerModeAdd").addClass("active");
+                $("#headerModeRemove").removeClass("active");
+                $("#headerValue").parent().show();
+            });
+
+            $("#headerModeRemove").on("click", function(){
+                $("#headerModeAdd").removeClass("active");
+                $("#headerModeRemove").addClass("active");
+                $("#headerValue").parent().hide();
+            });
+
+            //Bind events to header directions option
+            $("#toOriginButton").on("click", function(){
+                $("#toOriginButton").addClass("active");
+                $("#toClientButton").removeClass("active");
+            });
+
+            $("#toClientButton").on("click", function(){
+                $("#toOriginButton").removeClass("active");
+                $("#toClientButton").addClass("active");
+            });
+
+            //Return "add" or "remove" depending on mode user selected
+            function getHeaderEditMode(){
+                if ($("#headerModeAdd").hasClass("active")){
+                    return "add";
+                }
+
+                return "remove";
+            }
+
+            //Return "toOrigin" or "toClient"
+            function getHeaderDirection(){
+                if ($("#toOriginButton").hasClass("active")){
+                    return "toOrigin";
+                }
+                return "toClient";
+            }
+
             //$("#debug").text(JSON.stringify(editingEndpoint));
 
             function addCustomHeader(){
@@ -88,18 +140,21 @@
                     $("#headerName").parent().removeClass("error");
                 }
 
-                if (value == ""){
-                    $("#headerValue").parent().addClass("error");
-                    return
-                }else{
-                    $("#headerValue").parent().removeClass("error");
+                if (getHeaderEditMode() == "add"){
+                    if (value == ""){
+                        $("#headerValue").parent().addClass("error");
+                        return
+                    }else{
+                        $("#headerValue").parent().removeClass("error");
+                    }
                 }
 
                 $.ajax({
                     url: "/api/proxy/header/add",
                     data: {
-                        "type": editingEndpoint.ept,
+                        "type": getHeaderEditMode(),
                         "domain": editingEndpoint.ep,
+                        "direction":getHeaderDirection(),
                         "name": name,
                         "value": value
                     },
@@ -129,7 +184,7 @@
                 $.ajax({
                     url: "/api/proxy/header/remove",
                     data: {
-                        "type": editingEndpoint.ept,
+                        //"type": editingEndpoint.ept,
                         "domain": editingEndpoint.ep,
                         "name": name,
                     },
@@ -157,10 +212,16 @@
                            
                             $("#headerTable").html("");
                             data.forEach(header => {
+                                let editModeIcon = header.IsRemove?`<i class="ui red times circle icon"></i>`:`<i class="ui green add circle icon"></i>`;
+                                let direction = (header.Direction==0)?`<i class="angle double right blue icon"></i>`:`<i class="angle double left orange icon"></i>`;
+                                let valueField = header.Value;
+                                if (header.IsRemove){
+                                    valueField = "<small style='color: grey;'>(Field Removed)</small>";
+                                }
                                 $("#headerTable").append(`
                                 <tr>
-                                    <td>${header.Key}</td>
-                                    <td>${header.Value}</td>
+                                    <td>${direction} ${header.Key}</td>
+                                    <td>${editModeIcon} ${valueField}</td>
                                     <td><button class="ui basic circular mini red icon button" onclick="deleteCustomHeader('${header.Key}');"><i class="ui trash icon"></i></button></td>
                                 </tr>
                                 `);