Added Disable Chunk Transfer Encoding option

- Added disable chunk transfer encoding on UI #685
- Added optional to disable static web server listen to all interface #688
This commit is contained in:
Toby Chui 2025-06-15 13:46:35 +08:00
parent 31ba4f20ae
commit 4a37a989a0
10 changed files with 140 additions and 63 deletions

View File

@ -193,6 +193,7 @@ func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) {
authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer) authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer)
authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange) authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange)
authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing) authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing)
authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface)
/* File Manager */ /* File Manager */
if *allowWebFileManager { if *allowWebFileManager {
authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList) authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList)

View File

@ -70,8 +70,9 @@ type ResponseRewriteRuleSet struct {
DownstreamHeaders [][]string DownstreamHeaders [][]string
/* Advance Usecase Options */ /* Advance Usecase Options */
HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase) HostHeaderOverwrite string //Force overwrite of request "Host" header (advanced usecase)
NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase) NoRemoveHopByHop bool //Do not remove hop-by-hop headers (advanced usecase)
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding
/* System Information Payload */ /* System Information Payload */
Version string //Version number of Zoraxy, use for X-Proxy-By Version string //Version number of Zoraxy, use for X-Proxy-By
@ -287,7 +288,7 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version) rewriteUserAgent(outreq.Header, "Zoraxy/"+rrr.Version)
//Fix proxmox transfer encoding bug if detected Proxmox Cookie //Fix proxmox transfer encoding bug if detected Proxmox Cookie
if domainsniff.IsProxmox(req) { if rrr.DisableChunkedTransferEncoding || domainsniff.IsProxmox(req) {
outreq.TransferEncoding = []string{"identity"} outreq.TransferEncoding = []string{"identity"}
} }

View File

@ -11,7 +11,6 @@ import (
"sort" "sort"
"strings" "strings"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite" "imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/netutils" "imuslab.com/zoraxy/mod/netutils"
@ -186,16 +185,17 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
//Handle the request reverse proxy //Handle the request reverse proxy
statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ statusCode, err := selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: selectedUpstream.OriginIpOrDomain, ProxyDomain: selectedUpstream.OriginIpOrDomain,
OriginalHost: reqHostname, OriginalHost: reqHostname,
UseTLS: selectedUpstream.RequireTLS, UseTLS: selectedUpstream.RequireTLS,
NoCache: h.Parent.Option.NoCache, NoCache: h.Parent.Option.NoCache,
PathPrefix: "", PathPrefix: "",
UpstreamHeaders: upstreamHeaders, UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders, DownstreamHeaders: downstreamHeaders,
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, DisableChunkedTransferEncoding: target.DisableChunkedTransferEncoding,
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval, HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
Version: target.parent.Option.HostVersion, NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
Version: target.parent.Option.HostVersion,
}) })
//validate the error //validate the error
@ -244,8 +244,8 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain) h.Parent.logRequest(r, true, 101, "vdir-websocket", r.Host, target.Domain)
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{ wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
SkipTLSValidation: target.SkipCertValidations, SkipTLSValidation: target.SkipCertValidations,
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
CopyAllHeaders: domainsniff.RequireWebsocketHeaderCopy(r), //Left this as default to prevent nginx user setting / as vdir CopyAllHeaders: target.parent.EnableWebsocketCustomHeaders, //Left this as default to prevent nginx user setting / as vdir
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders, UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
Logger: h.Parent.Option.Logger, Logger: h.Parent.Option.Logger,
}) })
@ -280,14 +280,15 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
//Handle the virtual directory reverse proxy request //Handle the virtual directory reverse proxy request
statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{ statusCode, err := target.proxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
ProxyDomain: target.Domain, ProxyDomain: target.Domain,
OriginalHost: reqHostname, OriginalHost: reqHostname,
UseTLS: target.RequireTLS, UseTLS: target.RequireTLS,
PathPrefix: target.MatchingPath, PathPrefix: target.MatchingPath,
UpstreamHeaders: upstreamHeaders, UpstreamHeaders: upstreamHeaders,
DownstreamHeaders: downstreamHeaders, DownstreamHeaders: downstreamHeaders,
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite, DisableChunkedTransferEncoding: target.parent.DisableChunkedTransferEncoding,
Version: target.parent.parent.Option.HostVersion, HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
Version: target.parent.parent.Option.HostVersion,
}) })
var dnsError *net.DNSError var dnsError *net.DNSError

View File

@ -194,6 +194,9 @@ type ProxyEndpoint struct {
//Uptime Monitor //Uptime Monitor
DisableUptimeMonitor bool //Disable uptime monitor for this endpoint DisableUptimeMonitor bool //Disable uptime monitor for this endpoint
// Chunked Transfer Encoding
DisableChunkedTransferEncoding bool //Disable chunked transfer encoding for this endpoint
//Access Control //Access Control
AccessFilterUUID string //Access filter ID AccessFilterUUID string //Access filter ID

View File

@ -17,22 +17,24 @@ import (
*/ */
type StaticWebServerStatus struct { type StaticWebServerStatus struct {
ListeningPort int ListeningPort int
EnableDirectoryListing bool EnableDirectoryListing bool
WebRoot string WebRoot string
Running bool Running bool
EnableWebDirManager bool EnableWebDirManager bool
DisableListenToAllInterface bool
} }
// Handle getting current static web server status // Handle getting current static web server status
func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) { func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
listeningPortInt, _ := strconv.Atoi(ws.option.Port) listeningPortInt, _ := strconv.Atoi(ws.option.Port)
currentStatus := StaticWebServerStatus{ currentStatus := StaticWebServerStatus{
ListeningPort: listeningPortInt, ListeningPort: listeningPortInt,
EnableDirectoryListing: ws.option.EnableDirectoryListing, EnableDirectoryListing: ws.option.EnableDirectoryListing,
WebRoot: ws.option.WebRoot, WebRoot: ws.option.WebRoot,
Running: ws.isRunning, Running: ws.isRunning,
EnableWebDirManager: ws.option.EnableWebDirManager, EnableWebDirManager: ws.option.EnableWebDirManager,
DisableListenToAllInterface: ws.option.DisableListenToAllInterface,
} }
js, _ := json.Marshal(currentStatus) js, _ := json.Marshal(currentStatus)
@ -91,3 +93,19 @@ func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Re
ws.option.EnableDirectoryListing = enableList ws.option.EnableDirectoryListing = enableList
utils.SendOK(w) utils.SendOK(w)
} }
// Get or set disable listen to all interface settings
func (ws *WebServer) SetDisableListenToAllInterface(w http.ResponseWriter, r *http.Request) {
disableListen, err := utils.PostBool(r, "disable")
if err != nil {
utils.SendErrorResponse(w, "invalid setting given")
return
}
err = ws.option.Sysdb.Write("webserv", "disableListenToAllInterface", disableListen)
if err != nil {
utils.SendErrorResponse(w, "unable to save setting")
return
}
ws.option.DisableListenToAllInterface = disableListen
utils.SendOK(w)
}

View File

@ -25,13 +25,21 @@ import (
//go:embed templates/* //go:embed templates/*
var templates embed.FS var templates embed.FS
/*
WebServerOptions define the default option for the webserv
might get override by user settings loaded from db
Any changes in here might need to also update the StaticWebServerStatus struct
in handler.go. See handler.go for more information.
*/
type WebServerOptions struct { type WebServerOptions struct {
Port string //Port for listening Port string //Port for listening
EnableDirectoryListing bool //Enable listing of directory EnableDirectoryListing bool //Enable listing of directory
WebRoot string //Folder for stroing the static web folders WebRoot string //Folder for stroing the static web folders
EnableWebDirManager bool //Enable web file manager to handle files in web directory EnableWebDirManager bool //Enable web file manager to handle files in web directory
Logger *logger.Logger //System logger DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost
Sysdb *database.Database //Database for storing configs Logger *logger.Logger //System logger
Sysdb *database.Database //Database for storing configs
} }
type WebServer struct { type WebServer struct {
@ -92,6 +100,11 @@ func (ws *WebServer) RestorePreviousState() {
ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList) ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList)
ws.option.EnableDirectoryListing = enableDirList ws.option.EnableDirectoryListing = enableDirList
//Set disable listen to all interface
disableListenToAll := ws.option.DisableListenToAllInterface
ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll)
ws.option.DisableListenToAllInterface = disableListenToAll
//Check the running state //Check the running state
webservRunning := true webservRunning := true
ws.option.Sysdb.Read("webserv", "enabled", &webservRunning) ws.option.Sysdb.Read("webserv", "enabled", &webservRunning)
@ -156,8 +169,12 @@ func (ws *WebServer) Start() error {
fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html"))) fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html")))
ws.mux.Handle("/", ws.fsMiddleware(fs)) ws.mux.Handle("/", ws.fsMiddleware(fs))
listenAddr := ":" + ws.option.Port
if ws.option.DisableListenToAllInterface {
listenAddr = "127.0.0.1:" + ws.option.Port
}
ws.server = &http.Server{ ws.server = &http.Server{
Addr: ":" + ws.option.Port, Addr: listenAddr,
Handler: ws.mux, Handler: ws.mux,
} }

View File

@ -556,6 +556,9 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
proxyRateLimit = 1000 proxyRateLimit = 1000
} }
// Disable chunked Encoding
disableChunkedEncoding, _ := utils.PostBool(r, "dChunkedEnc")
//Load the previous basic auth credentials from current proxy rules //Load the previous basic auth credentials from current proxy rules
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain) targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
if err != nil { if err != nil {
@ -596,6 +599,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
newProxyEndpoint.RateLimit = proxyRateLimit newProxyEndpoint.RateLimit = proxyRateLimit
newProxyEndpoint.UseStickySession = useStickySession newProxyEndpoint.UseStickySession = useStickySession
newProxyEndpoint.DisableUptimeMonitor = disbleUtm newProxyEndpoint.DisableUptimeMonitor = disbleUtm
newProxyEndpoint.DisableChunkedTransferEncoding = disableChunkedEncoding
newProxyEndpoint.Tags = tags newProxyEndpoint.Tags = tags
//Prepare to replace the current routing rule //Prepare to replace the current routing rule

View File

@ -223,10 +223,10 @@
<div id="httprpEditModalSideMenu" class="four wide column"> <div id="httprpEditModalSideMenu" class="four wide column">
<div class="ui secondary fluid vertical menu"> <div class="ui secondary fluid vertical menu">
<a class="active item hrpedit_menu_item" cfgpage="downstream"> <a class="active item hrpedit_menu_item" cfgpage="downstream">
<i class="angle double white right icon"></i> <span class="editorSideMenuText">Downstream</span> <i class="home icon"></i> <span class="editorSideMenuText">Host</span>
</a> </a>
<a class="item hrpedit_menu_item" cfgpage="upstream"> <a class="item hrpedit_menu_item" cfgpage="upstream">
<i class="angle double left icon"></i> <span class="editorSideMenuText">Upstream</span> <i class="server icon"></i> <span class="editorSideMenuText">Destinations</span>
</a> </a>
<a class="item hrpedit_menu_item" cfgpage="vdirs"> <a class="item hrpedit_menu_item" cfgpage="vdirs">
<i class="angle folder icon"></i> <span class="editorSideMenuText">Virtual Directory</span> <i class="angle folder icon"></i> <span class="editorSideMenuText">Virtual Directory</span>
@ -256,7 +256,7 @@
</div> </div>
<div id="httprpEditModalContentWindow" class="twelve wide column"> <div id="httprpEditModalContentWindow" class="twelve wide column">
<div style="height:100%;"> <div style="height:100%;">
<!-- Downstream --> <!-- Host -->
<div class="rpconfig_content" rpcfg="downstream"> <div class="rpconfig_content" rpcfg="downstream">
<div class="ui segment"> <div class="ui segment">
<h3> <h3>
@ -288,10 +288,11 @@
</div> </div>
</div> </div>
<!-- Upstream --> <!-- Destinations -->
<div class="rpconfig_content" rpcfg="upstream"> <div class="rpconfig_content" rpcfg="upstream">
<div class="ui segment"> <div class="ui segment">
<div class="upstream_list"> <b>Enabled Upstreams</b>
<div class="upstream_list" style="margin-top: 0.4em;">
</div> </div>
<button class="ui basic compact button editUpstreamButton" style="margin-left: 0.4em; margin-top: 1em;"><i class="grey server icon"></i> Edit Upstreams</button> <button class="ui basic compact button editUpstreamButton" style="margin-left: 0.4em; margin-top: 1em;"><i class="grey server icon"></i> Edit Upstreams</button>
@ -308,17 +309,18 @@
<small>Enable stick session on load balancing</small></label> <small>Enable stick session on load balancing</small></label>
</div> </div>
<br> <br>
<div class="ui disabled checkbox" style="margin-top: 0.4em;"> <div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" class="DisableChunkedTransferEncoding"> <input type="checkbox" class="DisableChunkedTransferEncoding">
<label>Disable Chunked Transfer Encoding<br> <label>Disable Chunked Transfer Encoding<br>
<small>Enable this option if your upstream uses a legacy HTTP server implementation</small></label> <small>Enable this option if your upstream uses a legacy HTTP server implementation (e.g. Proxmox / opencloud)</small></label>
</div> </div>
</div> </div>
</div> </div>
<!-- Virtual Directories--> <!-- Virtual Directories-->
<div class="rpconfig_content" rpcfg="vdirs"> <div class="rpconfig_content" rpcfg="vdirs">
<div class="ui segment"> <div class="ui segment">
<div class="vdir_list"> <b>List of Virtual Directories</b>
<div class="vdir_list" style="margin-top:0.4em;">
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>
@ -719,6 +721,7 @@
let requireRateLimit = $(editor).find(".RequireRateLimit")[0].checked; let requireRateLimit = $(editor).find(".RequireRateLimit")[0].checked;
let rateLimit = $(editor).find(".RateLimit").val(); let rateLimit = $(editor).find(".RateLimit").val();
let bypassGlobalTLS = $(editor).find(".BypassGlobalTLS")[0].checked; let bypassGlobalTLS = $(editor).find(".BypassGlobalTLS")[0].checked;
let disableChunkedTransferEncoding = $(editor).find(".DisableChunkedTransferEncoding")[0].checked;
let tags = getTagsArrayFromEndpoint(uuid); let tags = getTagsArrayFromEndpoint(uuid);
if (tags.length > 0){ if (tags.length > 0){
tags = tags.join(","); tags = tags.join(",");
@ -726,7 +729,7 @@
tags = ""; tags = "";
} }
console.log({ cfgPayload = {
"type": epttype, "type": epttype,
"rootname": uuid, "rootname": uuid,
"ss":useStickySession, "ss":useStickySession,
@ -734,24 +737,16 @@
"bpgtls": bypassGlobalTLS, "bpgtls": bypassGlobalTLS,
"authprovider" :authProviderType, "authprovider" :authProviderType,
"rate" :requireRateLimit, "rate" :requireRateLimit,
"dChunkedEnc": disableChunkedTransferEncoding,
"ratenum" :rateLimit, "ratenum" :rateLimit,
"tags": tags, "tags": tags,
}); };
console.log("updating proxy config:", cfgPayload);
$.cjax({ $.cjax({
url: "/api/proxy/edit", url: "/api/proxy/edit",
method: "POST", method: "POST",
data: { data: cfgPayload,
"type": epttype,
"rootname": uuid,
"ss":useStickySession,
"dutm": DisableUptimeMonitor,
"bpgtls": bypassGlobalTLS,
"authprovider" :authProviderType,
"rate" :requireRateLimit,
"ratenum" :rateLimit,
"tags": tags,
},
success: function(data){ success: function(data){
if (data.error !== undefined){ if (data.error !== undefined){
msgbox(data.error, false, 6000); msgbox(data.error, false, 6000);
@ -1143,6 +1138,12 @@
saveProxyInlineEdit(uuid); saveProxyInlineEdit(uuid);
}); });
editor.find(".DisableChunkedTransferEncoding").off("change");
editor.find(".DisableChunkedTransferEncoding").prop("checked", subd.DisableChunkedTransferEncoding);
editor.find(".DisableChunkedTransferEncoding").on("change", function() {
saveProxyInlineEdit(uuid);
});
/* ------------ Vdirs ------------ */ /* ------------ Vdirs ------------ */
editor.find(".vdir_list").html(renderVirtualDirectoryList(subd)); editor.find(".vdir_list").html(renderVirtualDirectoryList(subd));
editor.find(".editVdirBtn").off("click").on("click", function(){ editor.find(".editVdirBtn").off("click").on("click", function(){

View File

@ -29,6 +29,13 @@
<small>If this folder do not contains any index files, list the directory of this folder.</small> <small>If this folder do not contains any index files, list the directory of this folder.</small>
</div> </div>
</div> </div>
<div class="inline field">
<div class="ui toggle checkbox">
<input id="webserv_enableAllInterfaces" type="checkbox" class="hidden">
<label>Listening to All Interfaces</label>
<small>When disabled, the web server will only listen to localhost (127.0.0.1) and only reachable via reverse proxy rules.</small>
</div>
</div>
<div class="field"> <div class="field">
<label>Document Root Folder</label> <label>Document Root Folder</label>
<input id="webserv_docRoot" type="text" readonly="true"> <input id="webserv_docRoot" type="text" readonly="true">
@ -136,6 +143,13 @@
$("#webserv_dirManager").remove(); $("#webserv_dirManager").remove();
} }
if (!data.DisableListenToAllInterface){
//Options on UI is flipped
$("#webserv_enableAllInterfaces").parent().checkbox("set checked");
}else{
$("#webserv_enableAllInterfaces").parent().checkbox("set unchecked");
}
$("#webserv_listenPort").val(data.ListeningPort); $("#webserv_listenPort").val(data.ListeningPort);
updateWebServLinkExample(data.ListeningPort); updateWebServLinkExample(data.ListeningPort);
@ -178,6 +192,23 @@
} }
}) })
}); });
$("#webserv_enableAllInterfaces").off("change").on("change", function(){
let disable = !$(this)[0].checked;
$.cjax({
url: "/api/webserv/disableListenAllInterface",
method: "POST",
data: {"disable": disable},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false);
}else{
msgbox("Listening interface setting updated");
}
}
})
});
$("#webserv_listenPort").off("change").on("change", function(){ $("#webserv_listenPort").off("change").on("change", function(){
let newPort = $(this).val(); let newPort = $(this).val();

View File

@ -22,7 +22,7 @@
<br> <br>
<div class="ui container"> <div class="ui container">
<p>Tags currently applied to this host name / proxy rule</p> <p>Tags currently applied to this host name / proxy rule</p>
<div style="max-height: 300px; overflow-y: scroll;"> <div>
<table class="ui compact basic unstackable celled table"> <table class="ui compact basic unstackable celled table">
<thead> <thead>
<tr> <tr>