diff --git a/src/def.go b/src/def.go index a5fba8e..9b717ef 100644 --- a/src/def.go +++ b/src/def.go @@ -45,7 +45,7 @@ const ( /* Build Constants */ SYSTEM_NAME = "Zoraxy" SYSTEM_VERSION = "3.2.1" - 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" diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index 7eef37e..8f858e4 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -105,7 +105,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp thisTransporter := http.DefaultTransport //Hack the default transporter to handle more connections - optimalConcurrentConnection := 256 if dpcOptions.MaxConcurrentConnection > 0 { optimalConcurrentConnection = dpcOptions.MaxConcurrentConnection @@ -137,18 +136,6 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp } } -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - switch { - case aslash && bslash: - return a + b[1:] - case !aslash && !bslash: - return a + "/" + b - } - return a + b -} - func joinURLPath(a, b *url.URL) (path, rawpath string) { apath, bpath := a.EscapedPath(), b.EscapedPath() aslash, bslash := strings.HasSuffix(apath, "/"), strings.HasPrefix(bpath, "/") diff --git a/src/mod/dynamicproxy/proxyRequestHandler.go b/src/mod/dynamicproxy/proxyRequestHandler.go index fffc303..36e22df 100644 --- a/src/mod/dynamicproxy/proxyRequestHandler.go +++ b/src/mod/dynamicproxy/proxyRequestHandler.go @@ -320,6 +320,7 @@ func (router *Router) logRequest(r *http.Request, succ bool, statusCode int, for UserAgent: r.UserAgent(), RequestURL: r.Host + r.RequestURI, Target: originalHostname, + Upstream: upstreamHostname, } router.Option.StatisticCollector.RecordRequest(requestInfo) }() diff --git a/src/mod/statistic/analytic/utils.go b/src/mod/statistic/analytic/utils.go index a373abb..f5b174a 100644 --- a/src/mod/statistic/analytic/utils.go +++ b/src/mod/statistic/analytic/utils.go @@ -36,6 +36,8 @@ func mergeDailySummaryExports(exports []*statistic.DailySummaryExport) *statisti Referer: make(map[string]int), UserAgent: make(map[string]int), RequestURL: make(map[string]int), + Downstreams: make(map[string]int), + Upstreams: make(map[string]int), } for _, export := range exports { @@ -66,6 +68,14 @@ func mergeDailySummaryExports(exports []*statistic.DailySummaryExport) *statisti for key, value := range export.RequestURL { mergedExport.RequestURL[key] += value } + + for key, value := range export.Downstreams { + mergedExport.Downstreams[key] += value + } + + for key, value := range export.Upstreams { + mergedExport.Upstreams[key] += value + } } return mergedExport diff --git a/src/mod/statistic/statistic.go b/src/mod/statistic/statistic.go index e9c7a4f..c5c8647 100644 --- a/src/mod/statistic/statistic.go +++ b/src/mod/statistic/statistic.go @@ -24,12 +24,14 @@ type DailySummary struct { ErrorRequest int64 //Invalid request of the day, including error or not found ValidRequest int64 //Valid request of the day //Type counters - ForwardTypes *sync.Map //Map that hold the forward types - RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter - RequestClientIp *sync.Map //Map that hold all unique request IPs - Referer *sync.Map //Map that store where the user was refered from - UserAgent *sync.Map //Map that store the useragent of the request - RequestURL *sync.Map //Request URL of the request object + ForwardTypes *sync.Map //Map that hold the forward types + RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter + RequestClientIp *sync.Map //Map that hold all unique request IPs + Referer *sync.Map //Map that store where the user was refered from + UserAgent *sync.Map //Map that store the useragent of the request + RequestURL *sync.Map //Request URL of the request object + DownstreamHostnames *sync.Map //Request count of downstream hostname + UpstreamHostnames *sync.Map //Forwarded request count of upstream hostname } type RequestInfo struct { @@ -42,6 +44,7 @@ type RequestInfo struct { UserAgent string //UserAgent of the downstream request RequestURL string //Request URL Target string //Target domain or hostname + Upstream string ////Upstream domain or hostname, if the request is forwarded to upstream } type CollectorOption struct { @@ -233,6 +236,24 @@ func (c *Collector) RecordRequest(ri RequestInfo) { } else { c.DailySummary.RequestURL.Store(ri.RequestURL, ru.(int)+1) } + + //Record the downstream hostname + //This is the hostname that the user visited, not the target domain + ds, ok := c.DailySummary.DownstreamHostnames.Load(ri.Target) + if !ok { + c.DailySummary.DownstreamHostnames.Store(ri.Target, 1) + } else { + c.DailySummary.DownstreamHostnames.Store(ri.Target, ds.(int)+1) + } + + //Record the upstream hostname + //This is the selected load balancer upstream hostname or ip + us, ok := c.DailySummary.UpstreamHostnames.Load(ri.Upstream) + if !ok { + c.DailySummary.UpstreamHostnames.Store(ri.Upstream, 1) + } else { + c.DailySummary.UpstreamHostnames.Store(ri.Upstream, us.(int)+1) + } }() //ADD MORE HERE IF NEEDED @@ -271,15 +292,17 @@ func (c *Collector) ScheduleResetRealtimeStats() chan bool { func NewDailySummary() *DailySummary { return &DailySummary{ - TotalRequest: 0, - ErrorRequest: 0, - ValidRequest: 0, - ForwardTypes: &sync.Map{}, - RequestOrigin: &sync.Map{}, - RequestClientIp: &sync.Map{}, - Referer: &sync.Map{}, - UserAgent: &sync.Map{}, - RequestURL: &sync.Map{}, + TotalRequest: 0, + ErrorRequest: 0, + ValidRequest: 0, + ForwardTypes: &sync.Map{}, + RequestOrigin: &sync.Map{}, + RequestClientIp: &sync.Map{}, + Referer: &sync.Map{}, + UserAgent: &sync.Map{}, + RequestURL: &sync.Map{}, + DownstreamHostnames: &sync.Map{}, + UpstreamHostnames: &sync.Map{}, } } diff --git a/src/mod/statistic/structconv.go b/src/mod/statistic/structconv.go index 7de5c98..f36f0f8 100644 --- a/src/mod/statistic/structconv.go +++ b/src/mod/statistic/structconv.go @@ -13,6 +13,21 @@ type DailySummaryExport struct { Referer map[string]int UserAgent map[string]int RequestURL map[string]int + Downstreams map[string]int + Upstreams map[string]int +} + +func SyncMapToMapStringInt(syncMap *sync.Map) map[string]int { + result := make(map[string]int) + syncMap.Range(func(key, value interface{}) bool { + strKey, okKey := key.(string) + intValue, okValue := value.(int) + if okKey && okValue { + result[strKey] = intValue + } + return true + }) + return result } func DailySummaryToExport(summary DailySummary) DailySummaryExport { @@ -26,77 +41,53 @@ func DailySummaryToExport(summary DailySummary) DailySummaryExport { Referer: make(map[string]int), UserAgent: make(map[string]int), RequestURL: make(map[string]int), + Downstreams: make(map[string]int), + Upstreams: make(map[string]int), } - summary.ForwardTypes.Range(func(key, value interface{}) bool { - export.ForwardTypes[key.(string)] = value.(int) - return true - }) - - summary.RequestOrigin.Range(func(key, value interface{}) bool { - export.RequestOrigin[key.(string)] = value.(int) - return true - }) - - summary.RequestClientIp.Range(func(key, value interface{}) bool { - export.RequestClientIp[key.(string)] = value.(int) - return true - }) - - summary.Referer.Range(func(key, value interface{}) bool { - export.Referer[key.(string)] = value.(int) - return true - }) - - summary.UserAgent.Range(func(key, value interface{}) bool { - export.UserAgent[key.(string)] = value.(int) - return true - }) - - summary.RequestURL.Range(func(key, value interface{}) bool { - export.RequestURL[key.(string)] = value.(int) - return true - }) + export.ForwardTypes = SyncMapToMapStringInt(summary.ForwardTypes) + export.RequestOrigin = SyncMapToMapStringInt(summary.RequestOrigin) + export.RequestClientIp = SyncMapToMapStringInt(summary.RequestClientIp) + export.Referer = SyncMapToMapStringInt(summary.Referer) + export.UserAgent = SyncMapToMapStringInt(summary.UserAgent) + export.RequestURL = SyncMapToMapStringInt(summary.RequestURL) + export.Downstreams = SyncMapToMapStringInt(summary.DownstreamHostnames) + export.Upstreams = SyncMapToMapStringInt(summary.UpstreamHostnames) return export } +func MapStringIntToSyncMap(m map[string]int) *sync.Map { + syncMap := &sync.Map{} + for k, v := range m { + syncMap.Store(k, v) + } + return syncMap +} + func DailySummaryExportToSummary(export DailySummaryExport) DailySummary { summary := DailySummary{ - TotalRequest: export.TotalRequest, - ErrorRequest: export.ErrorRequest, - ValidRequest: export.ValidRequest, - ForwardTypes: &sync.Map{}, - RequestOrigin: &sync.Map{}, - RequestClientIp: &sync.Map{}, - Referer: &sync.Map{}, - UserAgent: &sync.Map{}, - RequestURL: &sync.Map{}, + TotalRequest: export.TotalRequest, + ErrorRequest: export.ErrorRequest, + ValidRequest: export.ValidRequest, + ForwardTypes: &sync.Map{}, + RequestOrigin: &sync.Map{}, + RequestClientIp: &sync.Map{}, + Referer: &sync.Map{}, + UserAgent: &sync.Map{}, + RequestURL: &sync.Map{}, + DownstreamHostnames: &sync.Map{}, + UpstreamHostnames: &sync.Map{}, } - for k, v := range export.ForwardTypes { - summary.ForwardTypes.Store(k, v) - } - - for k, v := range export.RequestOrigin { - summary.RequestOrigin.Store(k, v) - } - - for k, v := range export.RequestClientIp { - summary.RequestClientIp.Store(k, v) - } - - for k, v := range export.Referer { - summary.Referer.Store(k, v) - } - - for k, v := range export.UserAgent { - summary.UserAgent.Store(k, v) - } - - for k, v := range export.RequestURL { - summary.RequestURL.Store(k, v) - } + summary.ForwardTypes = MapStringIntToSyncMap(export.ForwardTypes) + summary.RequestOrigin = MapStringIntToSyncMap(export.RequestOrigin) + summary.RequestClientIp = MapStringIntToSyncMap(export.RequestClientIp) + summary.Referer = MapStringIntToSyncMap(export.Referer) + summary.UserAgent = MapStringIntToSyncMap(export.UserAgent) + summary.RequestURL = MapStringIntToSyncMap(export.RequestURL) + summary.DownstreamHostnames = MapStringIntToSyncMap(export.Downstreams) + summary.UpstreamHostnames = MapStringIntToSyncMap(export.Upstreams) return summary } diff --git a/src/web/components/stats.html b/src/web/components/stats.html index 4f650b0..3d78467 100644 --- a/src/web/components/stats.html +++ b/src/web/components/stats.html @@ -184,7 +184,46 @@ - + +
+
+
+

Requested Hostnames

+

Most requested hostnames from downstream

+
+
+ + + + + + + + + +
HostnameRequests
+
+
+
+
+

Forwarded Upstreams

+

The Top 100 upstreams where the requests are forwarded to

+
+
+ + + + + + + + + +
Upstream EndpointRequests
+
+
+
+

Visitor Trend Analysis

@@ -263,6 +302,22 @@ //Render Referer header renderRefererTable(data.Referer); + if (data.Downstreams == null){ + //No downstream data to show + $("#stats_downstreamTable").html("No data"); + }else{ + //Render the downstream table + renderDownstreamTable(data.Downstreams); + } + + if (data.Upstreams == null){ + //No upstream data to show + $("#stats_upstreamTable").html("No data"); + }else{ + //Render the upstream table + renderUpstreamTable(data.Upstreams); + } + //Hide the trend graphs $("#trendGraphs").hide(); }); @@ -410,6 +465,46 @@ } } + function renderDownstreamTable(downstreamList){ + const sortedEntries = Object.entries(downstreamList).sort(([, valueA], [, valueB]) => valueB - valueA); + $("#stats_downstreamTable").html(""); + let endStop = 100; + if (sortedEntries.length < 100){ + endStop = sortedEntries.length; + } + for (var i = 0; i < endStop; i++) { + let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,""); + if (sortedEntries[i][0] == ""){ + //Root + referer = `( Unknown or Hidden)`; + } + $("#stats_downstreamTable").append(` + ${referer} + ${sortedEntries[i][1]} + `); + } + } + + function renderUpstreamTable(upstreamList){ + const sortedEntries = Object.entries(upstreamList).sort(([, valueA], [, valueB]) => valueB - valueA); + $("#stats_upstreamTable").html(""); + let endStop = 100; + if (sortedEntries.length < 100){ + endStop = sortedEntries.length; + } + for (var i = 0; i < endStop; i++) { + let referer = (decodeURIComponent(sortedEntries[i][0])).replace(/(<([^>]+)>)/ig,""); + if (sortedEntries[i][0] == ""){ + //Root + referer = `( Unknown or Hidden)`; + } + $("#stats_upstreamTable").append(` + ${referer} + ${sortedEntries[i][1]} + `); + } + } + function renderFileTypeGraph(requestURLs){ //Create the device chart let fileExtensions = {};