Skip to content

Commit 39d6d16

Browse files
committed
Updated plugin interface
- Updated plugin interface to support static path routing - Added autosave for statistic data (workaround for #561)
1 parent b7e3888 commit 39d6d16

36 files changed

+1398
-149
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ src/log/
4949
# plugins
5050
example/plugins/ztnc/ztnc.db
5151
example/plugins/ztnc/authtoken.secret
52+
example/plugins/ztnc/ztnc.db.lock

example/plugins/build_all.sh

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
#!/bin/bash
2+
# This script builds all the plugins in the current directory
3+
4+
echo "Copying zoraxy_plugin to all mods"
5+
for dir in ./*; do
6+
if [ -d "$dir" ]; then
7+
cp -r ../mod/plugins/zoraxy_plugin "$dir/mod"
8+
fi
9+
done
10+
211

312
# Iterate over all directories in the current directory
13+
echo "Running go mod tidy and go build for all directories"
414
for dir in */; do
515
if [ -d "$dir" ]; then
616
echo "Processing directory: $dir"
@@ -19,4 +29,4 @@ for dir in */; do
1929
fi
2030
done
2131

22-
echo "Build process completed for all directories."
32+
echo "Build process completed for all directories."

example/plugins/debugger/main.go

+39-17
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import (
99
)
1010

1111
const (
12-
PLUGIN_ID = "org.aroz.zoraxy.debugger"
13-
UI_PATH = "/debug"
12+
PLUGIN_ID = "org.aroz.zoraxy.debugger"
13+
UI_PATH = "/debug"
14+
STATIC_CAPTURE_INGRESS = "/s_capture"
1415
)
1516

1617
func main() {
@@ -28,15 +29,15 @@ func main() {
2829
VersionMinor: 0,
2930
VersionPatch: 0,
3031

31-
GlobalCapturePaths: []plugin.CaptureRule{
32+
StaticCapturePaths: []plugin.StaticCaptureRule{
3233
{
33-
CapturePath: "/debug_test", //Capture all traffic of all HTTP proxy rule
34-
IncludeSubPaths: true,
34+
CapturePath: "/test_a",
35+
},
36+
{
37+
CapturePath: "/test_b",
3538
},
3639
},
37-
GlobalCaptureIngress: "",
38-
AlwaysCapturePaths: []plugin.CaptureRule{},
39-
AlwaysCaptureIngress: "",
40+
StaticCaptureIngress: "/s_capture",
4041

4142
UIPath: UI_PATH,
4243

@@ -50,21 +51,42 @@ func main() {
5051
panic(err)
5152
}
5253

53-
// Register the shutdown handler
54-
plugin.RegisterShutdownHandler(func() {
55-
// Do cleanup here if needed
56-
fmt.Println("Debugger Terminated")
57-
})
54+
//Create a path handler for the capture paths
55+
pathRouter := plugin.NewPathRouter()
56+
pathRouter.SetDebugPrintMode(true)
57+
pathRouter.RegisterPathHandler("/test_a", http.HandlerFunc(HandleCaptureA))
58+
pathRouter.RegisterPathHandler("/test_b", http.HandlerFunc(HandleCaptureB))
59+
pathRouter.SetDefaultHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60+
//In theory this should never be called
61+
//but just in case the request is not captured by the path handlers
62+
//this will be the fallback handler
63+
w.Header().Set("Content-Type", "text/html")
64+
w.Write([]byte("This request is captured by the default handler!<br>Request URI: " + r.URL.String()))
65+
}))
66+
pathRouter.RegisterHandle(STATIC_CAPTURE_INGRESS, http.DefaultServeMux)
5867

5968
http.HandleFunc(UI_PATH+"/", RenderDebugUI)
60-
http.HandleFunc("/gcapture", HandleIngressCapture)
6169
fmt.Println("Debugger started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
6270
http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
6371
}
6472

6573
// Handle the captured request
66-
func HandleIngressCapture(w http.ResponseWriter, r *http.Request) {
67-
fmt.Fprint(w, "Capture request received")
74+
func HandleCaptureA(w http.ResponseWriter, r *http.Request) {
75+
for key, values := range r.Header {
76+
for _, value := range values {
77+
fmt.Printf("%s: %s\n", key, value)
78+
}
79+
}
80+
w.Header().Set("Content-Type", "text/html")
81+
w.Write([]byte("This request is captured by A handler!<br>Request URI: " + r.URL.String()))
82+
}
83+
84+
func HandleCaptureB(w http.ResponseWriter, r *http.Request) {
85+
for key, values := range r.Header {
86+
for _, value := range values {
87+
fmt.Printf("%s: %s\n", key, value)
88+
}
89+
}
6890
w.Header().Set("Content-Type", "text/html")
69-
w.Write([]byte("This request is captured by the debugger"))
91+
w.Write([]byte("This request is captured by the B handler!<br>Request URI: " + r.URL.String()))
7092
}

example/plugins/debugger/mod/zoraxy_plugin/embed_webserver.go

+22
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io/fs"
77
"net/http"
88
"net/url"
9+
"os"
910
"strings"
1011
"time"
1112
)
@@ -15,6 +16,8 @@ type PluginUiRouter struct {
1516
TargetFs *embed.FS //The embed.FS where the UI files are stored
1617
TargetFsPrefix string //The prefix of the embed.FS where the UI files are stored, e.g. /web
1718
HandlerPrefix string //The prefix of the handler used to route this router, e.g. /ui
19+
20+
terminateHandler func() //The handler to be called when the plugin is terminated
1821
}
1922

2023
// NewPluginEmbedUIRouter creates a new PluginUiRouter with embed.FS
@@ -104,3 +107,22 @@ func (p *PluginUiRouter) Handler() http.Handler {
104107
p.populateCSRFToken(r, http.FileServer(http.FS(subFS))).ServeHTTP(w, r)
105108
})
106109
}
110+
111+
// RegisterTerminateHandler registers the terminate handler for the PluginUiRouter
112+
// The terminate handler will be called when the plugin is terminated from Zoraxy plugin manager
113+
// if mux is nil, the handler will be registered to http.DefaultServeMux
114+
func (p *PluginUiRouter) RegisterTerminateHandler(termFunc func(), mux *http.ServeMux) {
115+
p.terminateHandler = termFunc
116+
if mux == nil {
117+
mux = http.DefaultServeMux
118+
}
119+
mux.HandleFunc(p.HandlerPrefix+"/term", func(w http.ResponseWriter, r *http.Request) {
120+
p.terminateHandler()
121+
w.WriteHeader(http.StatusOK)
122+
go func() {
123+
//Make sure the response is sent before the plugin is terminated
124+
time.Sleep(100 * time.Millisecond)
125+
os.Exit(0)
126+
}()
127+
})
128+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package zoraxy_plugin
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"sort"
7+
"strings"
8+
)
9+
10+
type PathRouter struct {
11+
enableDebugPrint bool
12+
pathHandlers map[string]http.Handler
13+
defaultHandler http.Handler
14+
}
15+
16+
// NewPathRouter creates a new PathRouter
17+
func NewPathRouter() *PathRouter {
18+
return &PathRouter{
19+
enableDebugPrint: false,
20+
pathHandlers: make(map[string]http.Handler),
21+
}
22+
}
23+
24+
// RegisterPathHandler registers a handler for a path
25+
func (p *PathRouter) RegisterPathHandler(path string, handler http.Handler) {
26+
path = strings.TrimSuffix(path, "/")
27+
p.pathHandlers[path] = handler
28+
}
29+
30+
// RemovePathHandler removes a handler for a path
31+
func (p *PathRouter) RemovePathHandler(path string) {
32+
delete(p.pathHandlers, path)
33+
}
34+
35+
// SetDefaultHandler sets the default handler for the router
36+
// This handler will be called if no path handler is found
37+
func (p *PathRouter) SetDefaultHandler(handler http.Handler) {
38+
p.defaultHandler = handler
39+
}
40+
41+
// SetDebugPrintMode sets the debug print mode
42+
func (p *PathRouter) SetDebugPrintMode(enable bool) {
43+
p.enableDebugPrint = enable
44+
}
45+
46+
func (p *PathRouter) RegisterHandle(capture_ingress string, mux *http.ServeMux) {
47+
if !strings.HasSuffix(capture_ingress, "/") {
48+
capture_ingress = capture_ingress + "/"
49+
}
50+
mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
51+
p.ServeHTTP(w, r)
52+
}))
53+
}
54+
55+
func (p *PathRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
56+
capturePath := r.Header.Get("X-Zoraxy-Capture")
57+
if capturePath != "" {
58+
if p.enableDebugPrint {
59+
fmt.Printf("Using capture path: %s\n", capturePath)
60+
}
61+
originalURI := r.Header.Get("X-Zoraxy-Uri")
62+
r.URL.Path = originalURI
63+
if handler, ok := p.pathHandlers[capturePath]; ok {
64+
handler.ServeHTTP(w, r)
65+
return
66+
}
67+
}
68+
p.defaultHandler.ServeHTTP(w, r)
69+
}
70+
71+
func (p *PathRouter) PrintRequestDebugMessage(r *http.Request) {
72+
if p.enableDebugPrint {
73+
fmt.Printf("Capture Request with path: %s \n\n**Request Headers** \n\n", r.URL.Path)
74+
keys := make([]string, 0, len(r.Header))
75+
for key := range r.Header {
76+
keys = append(keys, key)
77+
}
78+
sort.Strings(keys)
79+
for _, key := range keys {
80+
for _, value := range r.Header[key] {
81+
fmt.Printf("%s: %s\n", key, value)
82+
}
83+
}
84+
85+
fmt.Printf("\n\n**Request Details**\n\n")
86+
fmt.Printf("Method: %s\n", r.Method)
87+
fmt.Printf("URL: %s\n", r.URL.String())
88+
fmt.Printf("Proto: %s\n", r.Proto)
89+
fmt.Printf("Host: %s\n", r.Host)
90+
fmt.Printf("RemoteAddr: %s\n", r.RemoteAddr)
91+
fmt.Printf("RequestURI: %s\n", r.RequestURI)
92+
fmt.Printf("ContentLength: %d\n", r.ContentLength)
93+
fmt.Printf("TransferEncoding: %v\n", r.TransferEncoding)
94+
fmt.Printf("Close: %v\n", r.Close)
95+
fmt.Printf("Form: %v\n", r.Form)
96+
fmt.Printf("PostForm: %v\n", r.PostForm)
97+
fmt.Printf("MultipartForm: %v\n", r.MultipartForm)
98+
fmt.Printf("Trailer: %v\n", r.Trailer)
99+
fmt.Printf("RemoteAddr: %s\n", r.RemoteAddr)
100+
fmt.Printf("RequestURI: %s\n", r.RequestURI)
101+
102+
}
103+
}

example/plugins/debugger/mod/zoraxy_plugin/zoraxy_plugin.go

+15-38
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7-
"os/signal"
87
"strings"
9-
"syscall"
108
)
119

1210
/*
@@ -24,9 +22,9 @@ const (
2422
PluginType_Utilities PluginType = 1 //Utilities Plugin, used for utilities like Zerotier or Static Web Server that do not require interception with the dpcore
2523
)
2624

27-
type CaptureRule struct {
28-
CapturePath string `json:"capture_path"`
29-
IncludeSubPaths bool `json:"include_sub_paths"`
25+
type StaticCaptureRule struct {
26+
CapturePath string `json:"capture_path"`
27+
//To be expanded
3028
}
3129

3230
type ControlStatusCode int
@@ -74,23 +72,24 @@ type IntroSpect struct {
7472
*/
7573

7674
/*
77-
Global Capture Settings
78-
79-
Once plugin is enabled these rules always applies, no matter which HTTP Proxy rule it is enabled on
80-
This captures the whole traffic of Zoraxy
75+
Static Capture Settings
8176
77+
Once plugin is enabled these rules always applies to the enabled HTTP Proxy rule
78+
This is faster than dynamic capture, but less flexible
8279
*/
83-
GlobalCapturePaths []CaptureRule `json:"global_capture_path"` //Global traffic capture path of your plugin
84-
GlobalCaptureIngress string `json:"global_capture_ingress"` //Global traffic capture ingress path of your plugin (e.g. /g_handler)
80+
StaticCapturePaths []StaticCaptureRule `json:"static_capture_paths"` //Static capture paths of your plugin, see Zoraxy documentation for more details
81+
StaticCaptureIngress string `json:"static_capture_ingress"` //Static capture ingress path of your plugin (e.g. /s_handler)
8582

8683
/*
87-
Always Capture Settings
84+
Dynamic Capture Settings
8885
89-
Once the plugin is enabled on a given HTTP Proxy rule,
90-
these always applies
86+
Once plugin is enabled, these rules will be captured and forward to plugin sniff
87+
if the plugin sniff returns 280, the traffic will be captured
88+
otherwise, the traffic will be forwarded to the next plugin
89+
This is slower than static capture, but more flexible
9190
*/
92-
AlwaysCapturePaths []CaptureRule `json:"always_capture_path"` //Always capture path of your plugin when enabled on a HTTP Proxy rule (e.g. /myapp)
93-
AlwaysCaptureIngress string `json:"always_capture_ingress"` //Always capture ingress path of your plugin when enabled on a HTTP Proxy rule (e.g. /a_handler)
91+
DynamicCaptureSniff string `json:"dynamic_capture_sniff"` //Dynamic capture sniff path of your plugin (e.g. /d_sniff)
92+
DynamicCaptureIngress string `json:"dynamic_capture_ingress"` //Dynamic capture ingress path of your plugin (e.g. /d_handler)
9493

9594
/* UI Path for your plugin */
9695
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
@@ -174,25 +173,3 @@ func ServeAndRecvSpec(pluginSpect *IntroSpect) (*ConfigureSpec, error) {
174173
ServeIntroSpect(pluginSpect)
175174
return RecvConfigureSpec()
176175
}
177-
178-
/*
179-
180-
Shutdown handler
181-
182-
This function will register a shutdown handler for the plugin
183-
The shutdown callback will be called when the plugin is shutting down
184-
You can use this to clean up resources like closing database connections
185-
*/
186-
187-
func RegisterShutdownHandler(shutdownCallback func()) {
188-
// Set up a channel to receive OS signals
189-
sigChan := make(chan os.Signal, 1)
190-
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
191-
192-
// Start a goroutine to listen for signals
193-
go func() {
194-
<-sigChan
195-
shutdownCallback()
196-
os.Exit(0)
197-
}()
198-
}

example/plugins/helloworld/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"net/http"
88
"strconv"
99

10-
plugin "example.com/zoraxy/helloworld/zoraxy_plugin"
10+
plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin"
1111
)
1212

1313
const (

0 commit comments

Comments
 (0)