Updated plugin interface

- Updated plugin interface to support static path routing
- Added autosave for statistic data (workaround for #561)
This commit is contained in:
Toby Chui
2025-03-02 09:15:50 +08:00
parent b7e3888513
commit 39d6d16c2a
36 changed files with 1398 additions and 149 deletions

View File

@ -23,7 +23,6 @@ import (
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/email"
"imuslab.com/zoraxy/mod/forwardproxy"
"imuslab.com/zoraxy/mod/ganserv"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/info/logviewer"
@ -43,23 +42,24 @@ import (
const (
/* Build Constants */
SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.1.9"
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
SYSTEM_VERSION = "3.2.0"
DEVELOPMENT_BUILD = true /* Development: Set to false to use embedded web fs */
/* System Constants */
TMP_FOLDER = "./tmp"
WEBSERV_DEFAULT_PORT = 5487
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
MDNS_IDENTIFY_DEVICE_TYPE = "Network Gateway"
MDNS_IDENTIFY_DOMAIN = "zoraxy.aroz.org"
MDNS_IDENTIFY_VENDOR = "imuslab.com"
MDNS_SCAN_TIMEOUT = 30 /* Seconds */
MDNS_SCAN_UPDATE_INTERVAL = 15 /* Minutes */
GEODB_CACHE_CLEAR_INTERVAL = 15 /* Minutes */
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
CSRF_COOKIENAME = "zoraxy_csrf"
LOG_PREFIX = "zr"
LOG_EXTENSION = ".log"
TMP_FOLDER = "./tmp"
WEBSERV_DEFAULT_PORT = 5487
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
MDNS_IDENTIFY_DEVICE_TYPE = "Network Gateway"
MDNS_IDENTIFY_DOMAIN = "zoraxy.aroz.org"
MDNS_IDENTIFY_VENDOR = "imuslab.com"
MDNS_SCAN_TIMEOUT = 30 /* Seconds */
MDNS_SCAN_UPDATE_INTERVAL = 15 /* Minutes */
GEODB_CACHE_CLEAR_INTERVAL = 15 /* Minutes */
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
CSRF_COOKIENAME = "zoraxy_csrf"
LOG_PREFIX = "zr"
LOG_EXTENSION = ".log"
STATISTIC_AUTO_SAVE_INTERVAL = 600 /* Seconds */
/* Configuration Folder Storage Path Constants */
CONF_HTTP_PROXY = "./conf/proxy"
@ -132,7 +132,6 @@ var (
statisticCollector *statistic.Collector //Collecting statistic from visitors
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
mdnsScanner *mdns.MDNSHost //mDNS discovery services
ganManager *ganserv.NetworkManager //Global Area Network Manager
webSshManager *sshprox.Manager //Web SSH connection service
streamProxyManager *streamproxy.Manager //Stream Proxy Manager for TCP / UDP forwarding
acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew

View File

@ -27,6 +27,7 @@ require (
cloud.google.com/go/auth v0.13.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect

View File

@ -88,6 +88,8 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=
github.com/aws/aws-sdk-go-v2 v1.32.7/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
github.com/aws/aws-sdk-go-v2/config v1.28.7 h1:GduUnoTXlhkgnxTD93g1nv4tVPILbdNQOzav+Wpg7AE=

View File

@ -23,6 +23,7 @@ import (
- Rate Limitor
- SSO Auth
- Basic Auth
- Plugin Router
- Vitrual Directory Proxy
- Subdomain Proxy
- Root router (default site router)
@ -83,13 +84,19 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
//Validate basic auth
//Validate auth (basic auth or SSO auth)
respWritten := handleAuthProviderRouting(sep, w, r, h)
if respWritten {
//Request handled by subroute
return
}
//Plugin routing
if h.Parent.Option.PluginManager.HandleRoute(w, r, sep.Tags) {
//Request handled by subroute
return
}
//Check if any virtual directory rules matches
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)

View File

@ -22,6 +22,7 @@ import (
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/plugins"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/tlscert"
)
@ -59,6 +60,7 @@ type RouterOption struct {
StatisticCollector *statistic.Collector //Statistic collector for storing stats on incoming visitors
WebDirectory string //The static web server directory containing the templates folder
LoadBalancer *loadbalance.RouteManager //Load balancer that handle load balancing of proxy target
PluginManager *plugins.Manager //Plugin manager for handling plugin routing
/* Authentication Providers */
AutheliaRouter *authelia.AutheliaRouter //Authelia router for Authelia authentication

View File

@ -1,26 +0,0 @@
package plugins
import "net/http"
/*
Forwarder.go
This file handles the dynamic proxy routing forwarding
request to plugin capture path that handles the matching
request path registered when the plugin started
*/
func (m *Manager) GetHandlerPlugins(w http.ResponseWriter, r *http.Request) {
}
func (m *Manager) GetHandlerPluginsSubsets(w http.ResponseWriter, r *http.Request) {
}
func (p *Plugin) HandlePluginRoute(w http.ResponseWriter, r *http.Request) {
//Find the plugin that matches the request path
//If no plugin found, return 404
//If found, forward the request to the plugin
}

View File

@ -92,6 +92,9 @@ func (m *Manager) StartPlugin(pluginID string) error {
// Store the cmd object so it can be accessed later for stopping the plugin
plugin.(*Plugin).process = cmd
plugin.(*Plugin).Enabled = true
//Create a new static forwarder router for each of the static capture paths
plugin.(*Plugin).StartAllStaticPathRouters()
return nil
}
@ -198,6 +201,7 @@ func (m *Manager) StopPlugin(pluginID string) error {
//Remove the UI proxy
thisPlugin.uiProxy = nil
plugin.(*Plugin).Enabled = false
plugin.(*Plugin).StopAllStaticPathRouters()
return nil
}

View File

@ -11,10 +11,16 @@ package plugins
import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/utils"
)
@ -33,6 +39,7 @@ func NewPluginManager(options *ManagerOptions) *Manager {
return &Manager{
LoadedPlugins: sync.Map{},
TagPluginMap: sync.Map{},
Options: options,
}
}
@ -54,6 +61,7 @@ func (m *Manager) LoadPluginsFromDisk() error {
continue
}
thisPlugin.RootDir = filepath.ToSlash(pluginPath)
thisPlugin.staticRouteProxy = make(map[string]*dpcore.ReverseProxy)
m.LoadedPlugins.Store(thisPlugin.Spec.ID, thisPlugin)
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
@ -67,6 +75,9 @@ func (m *Manager) LoadPluginsFromDisk() error {
}
}
//Generate the static forwarder radix tree
m.UpdateTagsToTree()
return nil
}
@ -86,6 +97,8 @@ func (m *Manager) EnablePlugin(pluginID string) error {
return err
}
m.Options.Database.Write("plugins", pluginID, true)
//Generate the static forwarder radix tree
m.UpdateTagsToTree()
return nil
}
@ -96,6 +109,8 @@ func (m *Manager) DisablePlugin(pluginID string) error {
if err != nil {
return err
}
//Generate the static forwarder radix tree
m.UpdateTagsToTree()
return nil
}
@ -121,6 +136,16 @@ func (m *Manager) ListLoadedPlugins() ([]*Plugin, error) {
return plugins, nil
}
// Log a message with the plugin name
func (m *Manager) LogForPlugin(p *Plugin, message string, err error) {
processID := -1
if p.process != nil && p.process.Process != nil {
// Get the process ID of the plugin
processID = p.process.Process.Pid
}
m.Log("["+p.Spec.Name+":"+strconv.Itoa(processID)+"] "+message, err)
}
// Terminate all plugins and exit
func (m *Manager) Close() {
m.LoadedPlugins.Range(func(key, value interface{}) bool {
@ -134,3 +159,65 @@ func (m *Manager) Close() {
//Wait until all loaded plugin process are terminated
m.BlockUntilAllProcessExited()
}
/* Plugin Functions */
func (m *Plugin) StartAllStaticPathRouters() {
// Create a dpcore object for each of the static capture paths of the plugin
for _, captureRule := range m.Spec.StaticCapturePaths {
//Make sure the captureRule consists / prefix and no trailing /
if captureRule.CapturePath == "" {
continue
}
if !strings.HasPrefix(captureRule.CapturePath, "/") {
captureRule.CapturePath = "/" + captureRule.CapturePath
}
captureRule.CapturePath = strings.TrimSuffix(captureRule.CapturePath, "/")
// Create a new dpcore object to forward the traffic to the plugin
targetURL, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(m.AssignedPort) + m.Spec.StaticCaptureIngress)
if err != nil {
fmt.Println("Failed to parse target URL: "+targetURL.String(), err)
continue
}
thisRouter := dpcore.NewDynamicProxyCore(targetURL, captureRule.CapturePath, &dpcore.DpcoreOptions{})
m.staticRouteProxy[captureRule.CapturePath] = thisRouter
}
}
func (m *Plugin) StopAllStaticPathRouters() {
}
func (p *Plugin) HandleRoute(w http.ResponseWriter, r *http.Request, longestPrefix string) {
longestPrefix = strings.TrimSuffix(longestPrefix, "/")
targetRouter := p.staticRouteProxy[longestPrefix]
if targetRouter == nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
fmt.Println("Error: target router not found for prefix", longestPrefix)
return
}
originalRequestURI := r.RequestURI
//Rewrite the request path to the plugin UI path
rewrittenURL := r.RequestURI
rewrittenURL = strings.TrimPrefix(rewrittenURL, longestPrefix)
rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
if rewrittenURL == "" {
rewrittenURL = "/"
}
r.URL, _ = url.Parse(rewrittenURL)
targetRouter.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
UseTLS: false,
OriginalHost: r.Host,
ProxyDomain: "127.0.0.1:" + strconv.Itoa(p.AssignedPort),
NoCache: true,
PathPrefix: longestPrefix,
UpstreamHeaders: [][]string{
{"X-Zoraxy-Capture", longestPrefix},
{"X-Zoraxy-URI", originalRequestURI},
},
})
}

View File

@ -0,0 +1,82 @@
package plugins
import (
"errors"
"sync"
"github.com/armon/go-radix"
)
/*
Static Forwarder
This file handles the dynamic proxy routing forwarding
request to plugin capture path that handles the matching
request path registered when the plugin started
*/
func (m *Manager) UpdateTagsToTree() {
//build the tag to plugin pointer sync.Map
m.TagPluginMap = sync.Map{}
for tag, pluginIds := range m.Options.PluginGroups {
tree := m.GetForwarderRadixTreeFromPlugins(pluginIds)
m.TagPluginMap.Store(tag, tree)
}
}
// GenerateForwarderRadixTree generates the radix tree for static forwarders
func (m *Manager) GetForwarderRadixTreeFromPlugins(pluginIds []string) *radix.Tree {
// Create a new radix tree
r := radix.New()
// Iterate over the loaded plugins and insert their paths into the radix tree
m.LoadedPlugins.Range(func(key, value interface{}) bool {
plugin := value.(*Plugin)
if !plugin.Enabled {
//Ignore disabled plugins
return true
}
// Check if the plugin ID is in the list of plugin IDs
includeThisPlugin := false
for _, id := range pluginIds {
if plugin.Spec.ID == id {
includeThisPlugin = true
}
}
if !includeThisPlugin {
return true
}
//For each of the plugin, insert the requested static capture paths
if len(plugin.Spec.StaticCapturePaths) > 0 {
for _, captureRule := range plugin.Spec.StaticCapturePaths {
_, ok := r.Get(captureRule.CapturePath)
m.LogForPlugin(plugin, "Assigned static capture path: "+captureRule.CapturePath, nil)
if !ok {
//If the path does not exist, create a new list
newPluginList := make([]*Plugin, 0)
newPluginList = append(newPluginList, plugin)
r.Insert(captureRule.CapturePath, newPluginList)
} else {
//The path has already been assigned to another plugin
pluginList, _ := r.Get(captureRule.CapturePath)
//pluginList = append(pluginList.([]*Plugin), plugin)
//r.Insert(captureRule.CapturePath, pluginList)
//Warn the path is already assigned to another plugin
if plugin.Spec.ID == pluginList.([]*Plugin)[0].Spec.ID {
m.Log("Duplicate path register for plugin: "+plugin.Spec.Name+" ("+plugin.Spec.ID+")", errors.New("duplcated path: "+captureRule.CapturePath))
continue
}
incompatiblePluginAInfo := pluginList.([]*Plugin)[0].Spec.Name + " (" + pluginList.([]*Plugin)[0].Spec.ID + ")"
incompatiblePluginBInfo := plugin.Spec.Name + " (" + plugin.Spec.ID + ")"
m.Log("Incompatible plugins: "+incompatiblePluginAInfo+" and "+incompatiblePluginBInfo, errors.New("incompatible plugins found for path: "+captureRule.CapturePath))
}
}
}
return true
})
return r
}

View File

@ -0,0 +1,47 @@
package plugins
import (
"net/http"
"sync"
"github.com/armon/go-radix"
)
// HandleRoute handles the request to the plugin
// return true if the request is handled by the plugin
func (m *Manager) HandleRoute(w http.ResponseWriter, r *http.Request, tags []string) bool {
if len(tags) == 0 {
return false
}
//For each tag, check if the request path matches the static capture path
var wg sync.WaitGroup //Wait group for the goroutines
var handler []*Plugin //The handler for the request, can be multiple plugins
var longestPrefixAcrossAlltags string = "" //The longest prefix across all tags
for _, tag := range tags {
wg.Add(1)
go func(thisTag string) {
defer wg.Done()
//Get the radix tree for the tag
tree, ok := m.TagPluginMap.Load(thisTag)
if !ok {
return
}
//Check if the request path matches the static capture path
longestPrefix, pluginList, ok := tree.(*radix.Tree).LongestPrefix(r.URL.Path)
if ok {
if longestPrefix > longestPrefixAcrossAlltags {
longestPrefixAcrossAlltags = longestPrefix
handler = pluginList.([]*Plugin)
}
}
}(tag)
}
wg.Wait()
if len(handler) > 0 {
//Handle the request
handler[0].HandleRoute(w, r, longestPrefixAcrossAlltags)
return true
}
return false
}

View File

@ -21,13 +21,15 @@ type Plugin struct {
Enabled bool //Whether the plugin is enabled
//Runtime
AssignedPort int //The assigned port for the plugin
uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI
process *exec.Cmd //The process of the plugin
AssignedPort int //The assigned port for the plugin
uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI
staticRouteProxy map[string]*dpcore.ReverseProxy //Storing longest prefix => dpcore map for static route
process *exec.Cmd //The process of the plugin
}
type ManagerOptions struct {
PluginDir string
PluginDir string //The directory where the plugins are stored
PluginGroups map[string][]string //The plugin groups,key is the tag name and the value is an array of plugin IDs
SystemConst *zoraxyPlugin.RuntimeConstantValue
Database *database.Database
Logger *logger.Logger
@ -36,5 +38,6 @@ type ManagerOptions struct {
type Manager struct {
LoadedPlugins sync.Map //Storing *Plugin
TagPluginMap sync.Map //Storing *radix.Tree for each plugin tag
Options *ManagerOptions
}

View File

@ -38,6 +38,9 @@ func (m *Manager) HandlePluginUI(pluginID string, w http.ResponseWriter, r *http
rewrittenURL := r.RequestURI
rewrittenURL = strings.TrimPrefix(rewrittenURL, matchingPath)
rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
if rewrittenURL == "" {
rewrittenURL = "/"
}
r.URL, _ = url.Parse(rewrittenURL)
//Call the plugin UI handler

View File

@ -0,0 +1,103 @@
package zoraxy_plugin
import (
"fmt"
"net/http"
"sort"
"strings"
)
type PathRouter struct {
enableDebugPrint bool
pathHandlers map[string]http.Handler
defaultHandler http.Handler
}
// NewPathRouter creates a new PathRouter
func NewPathRouter() *PathRouter {
return &PathRouter{
enableDebugPrint: false,
pathHandlers: make(map[string]http.Handler),
}
}
// RegisterPathHandler registers a handler for a path
func (p *PathRouter) RegisterPathHandler(path string, handler http.Handler) {
path = strings.TrimSuffix(path, "/")
p.pathHandlers[path] = handler
}
// RemovePathHandler removes a handler for a path
func (p *PathRouter) RemovePathHandler(path string) {
delete(p.pathHandlers, path)
}
// SetDefaultHandler sets the default handler for the router
// This handler will be called if no path handler is found
func (p *PathRouter) SetDefaultHandler(handler http.Handler) {
p.defaultHandler = handler
}
// SetDebugPrintMode sets the debug print mode
func (p *PathRouter) SetDebugPrintMode(enable bool) {
p.enableDebugPrint = enable
}
func (p *PathRouter) RegisterHandle(capture_ingress string, mux *http.ServeMux) {
if !strings.HasSuffix(capture_ingress, "/") {
capture_ingress = capture_ingress + "/"
}
mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p.ServeHTTP(w, r)
}))
}
func (p *PathRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
capturePath := r.Header.Get("X-Zoraxy-Capture")
if capturePath != "" {
if p.enableDebugPrint {
fmt.Printf("Using capture path: %s\n", capturePath)
}
originalURI := r.Header.Get("X-Zoraxy-Uri")
r.URL.Path = originalURI
if handler, ok := p.pathHandlers[capturePath]; ok {
handler.ServeHTTP(w, r)
return
}
}
p.defaultHandler.ServeHTTP(w, r)
}
func (p *PathRouter) PrintRequestDebugMessage(r *http.Request) {
if p.enableDebugPrint {
fmt.Printf("Capture Request with path: %s \n\n**Request Headers** \n\n", r.URL.Path)
keys := make([]string, 0, len(r.Header))
for key := range r.Header {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
for _, value := range r.Header[key] {
fmt.Printf("%s: %s\n", key, value)
}
}
fmt.Printf("\n\n**Request Details**\n\n")
fmt.Printf("Method: %s\n", r.Method)
fmt.Printf("URL: %s\n", r.URL.String())
fmt.Printf("Proto: %s\n", r.Proto)
fmt.Printf("Host: %s\n", r.Host)
fmt.Printf("RemoteAddr: %s\n", r.RemoteAddr)
fmt.Printf("RequestURI: %s\n", r.RequestURI)
fmt.Printf("ContentLength: %d\n", r.ContentLength)
fmt.Printf("TransferEncoding: %v\n", r.TransferEncoding)
fmt.Printf("Close: %v\n", r.Close)
fmt.Printf("Form: %v\n", r.Form)
fmt.Printf("PostForm: %v\n", r.PostForm)
fmt.Printf("MultipartForm: %v\n", r.MultipartForm)
fmt.Printf("Trailer: %v\n", r.Trailer)
fmt.Printf("RemoteAddr: %s\n", r.RemoteAddr)
fmt.Printf("RequestURI: %s\n", r.RequestURI)
}
}

View File

@ -22,9 +22,9 @@ const (
PluginType_Utilities PluginType = 1 //Utilities Plugin, used for utilities like Zerotier or Static Web Server that do not require interception with the dpcore
)
type CaptureRule struct {
CapturePath string `json:"capture_path"`
IncludeSubPaths bool `json:"include_sub_paths"`
type StaticCaptureRule struct {
CapturePath string `json:"capture_path"`
//To be expanded
}
type ControlStatusCode int
@ -72,23 +72,24 @@ type IntroSpect struct {
*/
/*
Global Capture Settings
Once plugin is enabled these rules always applies, no matter which HTTP Proxy rule it is enabled on
This captures the whole traffic of Zoraxy
Static Capture Settings
Once plugin is enabled these rules always applies to the enabled HTTP Proxy rule
This is faster than dynamic capture, but less flexible
*/
GlobalCapturePaths []CaptureRule `json:"global_capture_path"` //Global traffic capture path of your plugin
GlobalCaptureIngress string `json:"global_capture_ingress"` //Global traffic capture ingress path of your plugin (e.g. /g_handler)
StaticCapturePaths []StaticCaptureRule `json:"static_capture_paths"` //Static capture paths of your plugin, see Zoraxy documentation for more details
StaticCaptureIngress string `json:"static_capture_ingress"` //Static capture ingress path of your plugin (e.g. /s_handler)
/*
Always Capture Settings
Dynamic Capture Settings
Once the plugin is enabled on a given HTTP Proxy rule,
these always applies
Once plugin is enabled, these rules will be captured and forward to plugin sniff
if the plugin sniff returns 280, the traffic will be captured
otherwise, the traffic will be forwarded to the next plugin
This is slower than static capture, but more flexible
*/
AlwaysCapturePaths []CaptureRule `json:"always_capture_path"` //Always capture path of your plugin when enabled on a HTTP Proxy rule (e.g. /myapp)
AlwaysCaptureIngress string `json:"always_capture_ingress"` //Always capture ingress path of your plugin when enabled on a HTTP Proxy rule (e.g. /a_handler)
DynamicCaptureSniff string `json:"dynamic_capture_sniff"` //Dynamic capture sniff path of your plugin (e.g. /d_sniff)
DynamicCaptureIngress string `json:"dynamic_capture_ingress"` //Dynamic capture ingress path of your plugin (e.g. /d_handler)
/* UI Path for your plugin */
UIPath string `json:"ui_path"` //UI path of your plugin (e.g. /ui), will proxy the whole subpath tree to Zoraxy Web UI as plugin UI

View File

@ -50,6 +50,8 @@ type CollectorOption struct {
type Collector struct {
rtdataStopChan chan bool
autoSaveTicker *time.Ticker
autSaveStop chan bool
DailySummary *DailySummary
Option *CollectorOption
}
@ -78,6 +80,35 @@ func NewStatisticCollector(option CollectorOption) (*Collector, error) {
return &thisCollector, nil
}
// Set the autosave duration, the collector will save the daily summary to database
// set saveInterval to 0 to disable autosave
func (c *Collector) SetAutoSave(saveInterval int) {
//Stop the current ticker if exists
if c.autSaveStop != nil {
c.autSaveStop <- true
}
if saveInterval == 0 {
return
}
c.autSaveStop = make(chan bool)
ticker := time.NewTicker(time.Duration(saveInterval) * time.Second)
c.autoSaveTicker = ticker
go func() {
for {
select {
case <-ticker.C:
c.SaveSummaryOfDay()
case <-c.autSaveStop:
ticker.Stop()
return
}
}
}()
}
// Write the current in-memory summary to database file
func (c *Collector) SaveSummaryOfDay() {
//When it is called in 0:00am, make sure it is stored as yesterday key
@ -122,7 +153,6 @@ func (c *Collector) Close() {
//Write the buffered data into database
c.SaveSummaryOfDay()
}
// Main function to record all the inbound traffics

View File

@ -108,6 +108,7 @@ func ReverseProxtInit() {
NoCache: developmentMode,
ListenOnPort80: listenOnPort80,
ForceHttpsRedirect: forceHttpsRedirect,
/* Routing Service Managers */
TlsManager: tlsCertManager,
RedirectRuleTable: redirectTable,
GeodbStore: geodbStore,
@ -116,7 +117,9 @@ func ReverseProxtInit() {
AccessController: accessController,
AutheliaRouter: autheliaRouter,
LoadBalancer: loadBalancer,
Logger: SystemWideLogger,
PluginManager: pluginManager,
/* Utilities */
Logger: SystemWideLogger,
})
if err != nil {
SystemWideLogger.PrintAndLog("proxy-config", "Unable to create dynamic proxy router", err)