3 Commits

Author SHA1 Message Date
Toby Chui
2c270640e9 Added #843
- Added checkbox for disabling uptime monitor in new proxy
2025-10-11 21:36:16 +08:00
Toby Chui
cf2cf18136 Added check for loopback proxy enable state
- Added check and show 521 if the loopback proxy endpoint is disabled
2025-10-10 15:51:00 +08:00
Toby Chui
e77f947d1d Added loopback proxy support
- Added support for shortcut loopback setup in local setups
2025-10-10 14:43:38 +08:00
7 changed files with 136 additions and 25 deletions

View File

@@ -92,7 +92,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
//Plugin routing
if h.Parent.Option.PluginManager != nil && h.Parent.Option.PluginManager.HandleRoute(w, r, sep.Tags) {
//Request handled by subroute
return

View File

@@ -438,7 +438,15 @@ func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) (in
if !strings.Contains(host, ":") {
host += ":443"
}
serverName := req.URL.Hostname()
serverName := ""
//if p.Transport != nil {
// if tr, ok := p.Transport.(*http.Transport); ok && tr.TLSClientConfig != nil && tr.TLSClientConfig.ServerName != "" {
// serverName = tr.TLSClientConfig.ServerName
// }
//}
if serverName == "" {
serverName = req.URL.Hostname()
}
// Connect with SNI offload
tlsConfig := &tls.Config{

View File

@@ -272,6 +272,11 @@ func (ep *ProxyEndpoint) Remove() error {
return nil
}
// Check if the proxy endpoint is enabled
func (ep *ProxyEndpoint) IsEnabled() bool {
return !ep.Disabled
}
// Write changes to runtime without respawning the proxy handler
// use prepare -> remove -> add if you change anything in the endpoint
// that effects the proxy routing src / dest

View File

@@ -12,6 +12,7 @@ import (
"strings"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/netutils"
"imuslab.com/zoraxy/mod/statistic"
@@ -95,27 +96,47 @@ func (router *Router) GetProxyEndpointFromHostname(hostname string) *ProxyEndpoi
return targetSubdomainEndpoint
}
// Clearn URL Path (without the http:// part) replaces // in a URL to /
func (router *Router) clearnURL(targetUrlOPath string) string {
return strings.ReplaceAll(targetUrlOPath, "//", "/")
}
// Rewrite URL rewrite the prefix part of a virtual directory URL with /
func (router *Router) rewriteURL(rooturl string, requestURL string) string {
rewrittenURL := requestURL
rewrittenURL = strings.TrimPrefix(rewrittenURL, strings.TrimSuffix(rooturl, "/"))
if strings.Contains(rewrittenURL, "//") {
rewrittenURL = router.clearnURL(rewrittenURL)
rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
}
return rewrittenURL
}
// upstreamHostSwap check if this loopback to one of the proxy rule in the system. If yes, do a shortcut target swap
// this prevents unnecessary external DNS lookup and connection, return true if swapped and request is already handled
// by the loopback handler. Only continue if return is false
func (h *ProxyHandler) upstreamHostSwap(w http.ResponseWriter, r *http.Request, selectedUpstream *loadbalance.Upstream) bool {
upstreamHostname := selectedUpstream.OriginIpOrDomain
if strings.Contains(upstreamHostname, ":") {
upstreamHostname = strings.Split(upstreamHostname, ":")[0]
}
loopbackProxyEndpoint := h.Parent.GetProxyEndpointFromHostname(upstreamHostname)
if loopbackProxyEndpoint != nil {
//This is a loopback request. Swap the target to the loopback target
//h.Parent.Option.Logger.PrintAndLog("proxy", "Detected a loopback request to self. Swap the target to "+loopbackProxyEndpoint.RootOrMatchingDomain, nil)
if loopbackProxyEndpoint.IsEnabled() {
h.hostRequest(w, r, loopbackProxyEndpoint)
} else {
//Endpoint disabled, return 503
http.ServeFile(w, r, "./web/rperror.html")
h.Parent.logRequest(r, false, 521, "host-http", r.Host, upstreamHostname)
}
return true
}
return false
}
// Handle host request
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)
reqHostname := r.Host
/* Load balancing */
selectedUpstream, err := h.Parent.loadBalancer.GetRequestUpstreamTarget(w, r, target.ActiveOrigins, target.UseStickySession)
if err != nil {
@@ -125,6 +146,12 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
return
}
/* Upstream Host Swap (use to detect loopback to self) */
if h.upstreamHostSwap(w, r, selectedUpstream) {
//Request handled by the loopback handler
return
}
/* WebSocket automatic proxy */
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {

View File

@@ -211,7 +211,6 @@ func getWebsiteStatus(url string) (int, error) {
}
resp, err := client.Do(req)
//resp, err := client.Get(url)
if err != nil {
//Try replace the http with https and vise versa
rewriteURL := ""

View File

@@ -238,6 +238,13 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
bypassGlobalTLS, _ := utils.PostPara(r, "bypassGlobalTLS")
if bypassGlobalTLS == "" {
bypassGlobalTLS = "false"
}
// Enable uptime monitor?
enableUtm, err := utils.PostBool(r, "enableUtm")
if err != nil {
enableUtm = true
}
useBypassGlobalTLS := bypassGlobalTLS == "true"
@@ -410,7 +417,8 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
RequireRateLimit: requireRateLimit,
RateLimit: int64(proxyRateLimit),
Tags: tags,
Tags: tags,
DisableUptimeMonitor: !enableUtm,
}
preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint)

View File

@@ -62,6 +62,13 @@
<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="enableUtm" checked>
<label>Enable uptime monitor<br><small>Automatically check upstream status and switch to another if offline</small>
</label>
</div>
</div>
<div class="field">
<label>Tags</label>
@@ -168,22 +175,78 @@
</div>
<div class="six wide column">
<div class="ui basic segment rulesInstructions">
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Domain</span><br>
Example of domain matching keyword:<br>
<code>aroz.org</code> <br>Any acess requesting aroz.org will be proxy to the IP address below<br>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Subdomain</span><br>
Example of subdomain matching keyword:<br>
<code>s1.aroz.org</code> <br>Any request starting with s1.aroz.org will be proxy to the IP address below<br>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Wildcard</span><br>
Example of wildcard matching keyword:<br>
<code>*.aroz.org</code> <br>Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.<br>
<div class="ui list">
<div class="item"><code>www.aroz.org</code></div>
<div class="item"><code>foo.bar.aroz.org</code></div>
<div class="ui fluid styled accordion" id="matchingKeywordExamplesAccordion" style="background-color: transparent !important;">
<div class="title active" style="color: white;">
<i class="dropdown icon"></i>
Matching Keyword Examples
</div>
<div class="content active">
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Domain</span><br>
Example of domain matching keyword:<br>
<code>aroz.org</code> <br>Any acess requesting aroz.org will be proxy to the IP address below<br>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Subdomain</span><br>
Example of subdomain matching keyword:<br>
<code>s1.aroz.org</code> <br>Any request starting with s1.aroz.org will be proxy to the IP address below<br>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Wildcard</span><br>
Example of wildcard matching keyword:<br>
<code>*.aroz.org</code> <br>Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.<br>
<div class="ui list">
<div class="item"><code>www.aroz.org</code></div>
<div class="item"><code>foo.bar.aroz.org</code></div>
</div>
<br>
</div>
<div class="title" style="color: white;">
<i class="dropdown icon"></i>
Remote Target Require TLS
</div>
<div class="content">
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui green lock icon"></i> Upstream TLS Requirement</span><br>
<p>
When you enable <b>Proxy Target require TLS Connection</b>, it means the <b>upstream server</b> (the target you are proxying to) requires a secure (HTTPS) connection.<br>
<b>This does not affect whether clients connect to this proxy endpoint using HTTP or HTTPS.</b>
</p>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Example</span><br>
<code>Matching Keyword: mydomain.com<br>
Target: example.com:443 (TLS enabled)</code><br>
<ul>
<li>Client connects to <b>mydomain.com</b> (HTTP or HTTPS, depending on your proxy setup)</li>
<li>Proxy forwards requests to <b>example.com:443</b> using <b>HTTPS</b></li>
</ul>
<small>
Use this option if your upstream server only accepts secure connections.<br>
If your upstream uses a self-signed certificate, check the <b>Ignore TLS/SSL Verification Error</b> option in Advance Settings.
</small>
</div>
<div class="title" style="color: white;">
<i class="dropdown icon"></i>
What is Sticky Session?
</div>
<div class="content">
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui green sync icon"></i> Sticky Session (Session Affinity)</span><br>
<p>
Sticky session ensures that requests from the same client are always forwarded to the same upstream server. This is useful for applications that store session data locally and require the client to consistently connect to the same backend.<br>
</p>
<div class="ui divider"></div>
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> How to Add Multiple Upstreams</span><br>
<ul>
<li>Go to <b>HTTP Proxy</b> in the sidebar.</li>
<li>Click <b>Edit</b> on your proxy rule.</li>
<li>Use the <b>Upstreams</b> section to add more upstream endpoints for load balancing.</li>
</ul>
<small>
Sticky session will only work if you have more than one upstream endpoint configured.
</small>
</div>
</div>
<br>
<script>
$('#matchingKeywordExamplesAccordion').accordion();
</script>
</div>
</div>
</div>
@@ -204,6 +267,7 @@
let accessRuleToUse = $("#newProxyRuleAccessFilter").val();
let useStickySessionLB = $("#useStickySessionLB")[0].checked;
let tags = $("#proxyTags").val().trim();
let enableUtm = $("#enableUtm")[0].checked;
if (rootname.trim() == ""){
$("#rootname").parent().addClass("error");
@@ -238,6 +302,7 @@
access: accessRuleToUse,
stickysess: useStickySessionLB,
tags: tags,
enableUtm: enableUtm,
},
success: function(data){
if (data.error != undefined){