Browse Source

Added editable timeout values

Toby Chui 1 month ago
parent
commit
0edc1e1ff3

+ 1 - 1
def.go

@@ -43,7 +43,7 @@ const (
 	/* Build Constants */
 	SYSTEM_NAME       = "Zoraxy"
 	SYSTEM_VERSION    = "3.1.8"
-	DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
+	DEVELOPMENT_BUILD = true /* Development: Set to false to use embedded web fs */
 
 	/* System Constants */
 	TMP_FOLDER                 = "./tmp"

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

@@ -83,9 +83,12 @@ type requestCanceler interface {
 }
 
 type DpcoreOptions struct {
-	IgnoreTLSVerification bool          //Disable all TLS verification when request pass through this proxy router
-	FlushInterval         time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately)
-	UseH2CRoundTripper    bool          //Use H2C RoundTripper for HTTP/2.0 connection
+	IgnoreTLSVerification   bool          //Disable all TLS verification when request pass through this proxy router
+	FlushInterval           time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately)
+	MaxConcurrentConnection int           //Maxmium concurrent requests to this server
+	ResponseHeaderTimeout   int64         //Timeout for response header, set to 0 for default
+	IdleConnectionTimeout   int64         //Idle connection timeout, set to 0 for default
+	UseH2CRoundTripper      bool          //Use H2C RoundTripper for HTTP/2.0 connection
 }
 
 func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
@@ -106,18 +109,30 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
 
 	//Hack the default transporter to handle more connections
 	optimalConcurrentConnection := 32
+	if dpcOptions.MaxConcurrentConnection > 0 {
+		optimalConcurrentConnection = dpcOptions.MaxConcurrentConnection
+	}
+	thisTransporter.(*http.Transport).IdleConnTimeout = 30 * time.Second
 	thisTransporter.(*http.Transport).MaxIdleConns = optimalConcurrentConnection * 2
 	thisTransporter.(*http.Transport).MaxIdleConnsPerHost = optimalConcurrentConnection
-	thisTransporter.(*http.Transport).IdleConnTimeout = 30 * time.Second
 	thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
 	thisTransporter.(*http.Transport).DisableCompression = true
 
+	if dpcOptions.ResponseHeaderTimeout > 0 {
+		//Set response header timeout
+		thisTransporter.(*http.Transport).ResponseHeaderTimeout = time.Duration(dpcOptions.ResponseHeaderTimeout) * time.Millisecond
+	}
+
+	if dpcOptions.IdleConnectionTimeout > 0 {
+		//Set idle connection timeout
+		thisTransporter.(*http.Transport).IdleConnTimeout = time.Duration(dpcOptions.IdleConnectionTimeout) * time.Millisecond
+	}
+
 	if dpcOptions.IgnoreTLSVerification {
 		//Ignore TLS certificate validation error
 		thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
 	}
 
-	//TODO: Add user adjustable timeout option here
 	if dpcOptions.UseH2CRoundTripper {
 		//Use H2C RoundTripper for HTTP/2.0 connection
 		thisTransporter = modh2c.NewH2CRoundTripper()

+ 6 - 2
mod/dynamicproxy/loadbalance/loadbalance.go

@@ -43,8 +43,12 @@ type Upstream struct {
 	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 //TODO: Maxmium connection to this server, 0 for unlimited
+	Weight int //Random weight for round robin, 0 for fallback only
+
+	//HTTP Transport Config
+	MaxConn     int   //Maxmium concurrent requests to this upstream dpcore instance
+	RespTimeout int64 //Response header timeout in milliseconds
+	IdleTimeout int64 //Idle connection timeout in milliseconds
 
 	//currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
 	proxy *dpcore.ReverseProxy

+ 5 - 2
mod/dynamicproxy/loadbalance/upstream.go

@@ -39,8 +39,11 @@ func (u *Upstream) StartProxy() error {
 	}
 
 	proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
-		IgnoreTLSVerification: u.SkipCertValidations,
-		FlushInterval:         100 * time.Millisecond,
+		IgnoreTLSVerification:   u.SkipCertValidations,
+		FlushInterval:           100 * time.Millisecond,
+		ResponseHeaderTimeout:   u.RespTimeout,
+		IdleConnectionTimeout:   u.IdleTimeout,
+		MaxConcurrentConnection: u.MaxConn,
 	})
 
 	u.proxy = proxy

+ 22 - 1
upstreams.go

@@ -79,6 +79,25 @@ func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
 		utils.SendErrorResponse(w, "upstream origin not set")
 		return
 	}
+
+	//Response timeout in seconds, set to 0 for default
+	respTimeout, err := utils.PostInt(r, "respt")
+	if err != nil {
+		respTimeout = 0
+	}
+
+	//Idle timeout in seconds, set to 0 for default
+	idleTimeout, err := utils.PostInt(r, "idlet")
+	if err != nil {
+		idleTimeout = 0
+	}
+
+	//Max concurrent connection to dpcore instance, set to 0 for default
+	maxConn, err := utils.PostInt(r, "maxconn")
+	if err != nil {
+		maxConn = 0
+	}
+
 	requireTLS, _ := utils.PostBool(r, "tls")
 	skipTlsValidation, _ := utils.PostBool(r, "tlsval")
 	bpwsorg, _ := utils.PostBool(r, "bpwsorg")
@@ -91,7 +110,9 @@ func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
 		SkipCertValidations:      skipTlsValidation,
 		SkipWebSocketOriginCheck: bpwsorg,
 		Weight:                   1,
-		MaxConn:                  0,
+		MaxConn:                  maxConn,
+		RespTimeout:              int64(respTimeout),
+		IdleTimeout:              int64(idleTimeout),
 	}
 
 	//Add the new upstream to endpoint

+ 129 - 0
web/snippet/upstreams.html

@@ -57,6 +57,17 @@
                     margin-bottom: 0.4em;
                 }
             }
+
+            .advanceUpstreamOptions{
+                padding: 0.6em;
+                background-color: var(--theme_advance); 
+                width: 100%;
+                border-radius: 0.4em;
+            }
+
+            .advanceUpstreamOptions.ui.accordion .content{
+                padding: 1em !important;
+            }
         </style>
     </head>
     <body>
@@ -121,6 +132,38 @@
                     <label>Skip WebSocket Origin Check<br>
                     <small>Check this to allow cross-origin websocket requests</small></label>
                 </div>
+                <div class="ui advanceUpstreamOptions accordion" style="margin-top:0.6em;">
+                    <div class="title">
+                        <i class="dropdown icon"></i>
+                        Advanced Options
+                    </div>
+                    <div class="content">
+                        <p>Max Concurrent Connections</p>
+                        <div class="ui mini fluid input" style="margin-top: -0.6em;">
+                            <input type="number" min="0" id="maxConn" value="0">
+                        </div>
+                        <small>Set to 0 for default value (32 connections)</small>
+                        <br><br>
+                        <p>Response Timeout</p>
+                        <div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
+                            <input type="number" min="0" id="respTimeout" value="0">
+                            <div class="ui basic label">
+                                Seconds
+                            </div>
+                        </div>
+                        <small>Maximum waiting time for server header response, set to 0 for default</small>
+                        <br><br>
+                        <p>Idle Timeout</p>
+                        <div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
+                            <input type="number" min="0" id="idleTimeout" value="0">
+                            <div class="ui basic label">
+                                Seconds
+                            </div>
+                        </div>
+                        <small>Maximum allowed keep-alive time forcefully closes the connection, set to 0 for default</small>
+                    </div>
+                </div>
+                
                 <br><br>
                 <button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
             </div>
@@ -172,6 +215,8 @@
                                 renderUpstreamEntryToTable(upstream, false);
                             });
 
+                            $(".advanceUpstreamOptions.accordion").accordion();
+
                             let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
                             if (totalUpstreams == 1){
                                 $(".lowPriorityButton").addClass('disabled');
@@ -227,6 +272,8 @@
                 let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
                 let payload = encodeURIComponent(JSON.stringify(upstream));
                 let domUID = newUID();
+
+                //Timeout values are stored as ms in the backend
                 $("#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;">
@@ -262,6 +309,39 @@
                             <label>Skip WebSocket Origin Check<br>
                             <small>Check this to allow cross-origin websocket requests</small></label>
                         </div><br>
+                        <!-- Advance Settings -->
+                         <div class="ui advanceUpstreamOptions accordion" style="margin-top:0.6em;">
+                            <div class="title">
+                                <i class="dropdown icon"></i>
+                                Advanced Options
+                            </div>
+                            <div class="content">
+                                <p>Max Concurrent Connections</p>
+                                <div class="ui mini fluid input" style="margin-top: -0.6em;">
+                                    <input type="number" min="0" class="maxConn" value="${upstream.MaxConn}">
+                                </div>
+                                <small>Set to 0 for default value (32 connections)</small>
+                                <br>
+                                <p style="margin-top: 0.6em;">Response Timeout</p>
+                                <div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
+                                    <input type="number" min="0" class="respTimeout" value="${upstream.RespTimeout/1000}">
+                                     <div class="ui basic label">
+                                        Seconds
+                                    </div>
+                                </div>
+                                <small>Maximum waiting time before Zoraxy receive server header response, set to 0 for default</small>
+                                <br>
+                                <p style="margin-top: 0.6em;">Idle Timeout</p>
+                                <div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
+                                    <input type="number" min="0" class="idleTimeout" value="${upstream.IdleTimeout/1000}">
+                                     <div class="ui basic label">
+                                        Seconds
+                                    </div>
+                                </div>
+                                <small>Maximum allowed keep-alive time before Zoraxy forcefully close the connection, set to 0 for default</small>
+
+                            </div>
+                        </div>
                     </div>
                     <div class="upstreamActions">
                         <!-- Change Priority -->
@@ -316,12 +396,32 @@
                 let skipVerification = $("#skipTlsVerification")[0].checked;
                 let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
                 let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
+                let maxConn = $("#maxConn").val();
+                let respTimeout = $("#respTimeout").val();
+                let idleTimeout = $("#idleTimeout").val();
+
+                if (maxConn == "" || isNaN(maxConn)){ 
+                    maxConn = 0;
+                }
+
+                if (respTimeout == "" || isNaN(respTimeout)){
+                    respTimeout = 0;
+                }
+
+                if (idleTimeout == "" || isNaN(idleTimeout)){
+                    idleTimeout = 0;
+                }
+
 
                 if (origin == ""){
                     parent.msgbox("Upstream origin cannot be empty", false);
                     return;
                 }
 
+                //Convert seconds to ms
+                respTimeout = parseInt(respTimeout) * 1000;
+                idleTimeout = parseInt(idleTimeout) * 1000;
+
                 $.cjax({
                     url: "/api/proxy/upstream/add",
                     method: "POST",
@@ -332,6 +432,9 @@
                         "tlsval": skipVerification,
                         "bpwsorg":skipWebSocketOriginCheck,
                         "active": activateLoadbalancer,
+                        "maxconn": maxConn,
+                        "respt": respTimeout,
+                        "idlet": idleTimeout,
                     },
                     success: function(data){
                         if (data.error != undefined){
@@ -340,6 +443,9 @@
                             parent.msgbox("New upstream origin added");
                             initOriginList();
                             $("#originURL").val("");
+                            $("#maxConn").val("0");
+                            $("#respTimeout").val("0");
+                            $("#idleTimeout").val("0");
                         }
                     }
                 })
@@ -356,11 +462,34 @@
                 let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
                 let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
 
+                //Advance options
+                let maxConn = $(upstream).find(".maxConn").val();
+                let respTimeout = $(upstream).find(".respTimeout").val();
+                let idleTimeout = $(upstream).find(".idleTimeout").val();
+
+                if (maxConn == "" || isNaN(maxConn)){ 
+                    maxConn = 0;
+                }
+
+                if (respTimeout == "" || isNaN(respTimeout)){
+                    respTimeout = 0;
+                }
+
+                if (idleTimeout == "" || isNaN(idleTimeout)){
+                    idleTimeout = 0;
+                }
+
+                respTimeout = parseInt(respTimeout) * 1000;
+                idleTimeout = parseInt(idleTimeout) * 1000;
+
                 //Update the original setting with new one just applied
                 originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
                 originalSettings.RequireTLS = requireTLS;
                 originalSettings.SkipCertValidations = skipTLSVerification;
                 originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
+                originalSettings.MaxConn = parseInt(maxConn);
+                originalSettings.RespTimeout = respTimeout;
+                originalSettings.IdleTimeout = idleTimeout;
 
                 //console.log(originalSettings);
                 return originalSettings;