mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-27 01:41:44 +02:00
Updated plugin interface
- Updated plugin interface to support static path routing - Added autosave for statistic data (workaround for #561)
This commit is contained in:
33
src/def.go
33
src/def.go
@ -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
|
||||
|
@ -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
|
||||
|
@ -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=
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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},
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
|
82
src/mod/plugins/static_forwarder.go
Normal file
82
src/mod/plugins/static_forwarder.go
Normal 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
|
||||
}
|
47
src/mod/plugins/static_router.go
Normal file
47
src/mod/plugins/static_router.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
103
src/mod/plugins/zoraxy_plugin/static_router.go
Normal file
103
src/mod/plugins/zoraxy_plugin/static_router.go
Normal 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)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user