diff --git a/example/plugins/debugger/main.go b/example/plugins/debugger/main.go
index 84631cc..fc9e5b7 100644
--- a/example/plugins/debugger/main.go
+++ b/example/plugins/debugger/main.go
@@ -3,7 +3,9 @@ package main
import (
"fmt"
"net/http"
+ "sort"
"strconv"
+ "strings"
plugin "aroz.org/zoraxy/debugger/mod/zoraxy_plugin"
)
@@ -39,6 +41,9 @@ func main() {
},
StaticCaptureIngress: "/s_capture",
+ DynamicCaptureSniff: "/d_sniff",
+ DynamicCaptureIngress: "/d_capture",
+
UIPath: UI_PATH,
/*
@@ -51,9 +56,13 @@ func main() {
panic(err)
}
- //Create a path handler for the capture paths
+ // Setup the path router
pathRouter := plugin.NewPathRouter()
pathRouter.SetDebugPrintMode(true)
+
+ /*
+ Static Routers
+ */
pathRouter.RegisterPathHandler("/test_a", http.HandlerFunc(HandleCaptureA))
pathRouter.RegisterPathHandler("/test_b", http.HandlerFunc(HandleCaptureB))
pathRouter.SetDefaultHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -63,7 +72,46 @@ func main() {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte("This request is captured by the default handler!
Request URI: " + r.URL.String()))
}))
- pathRouter.RegisterHandle(STATIC_CAPTURE_INGRESS, http.DefaultServeMux)
+ pathRouter.RegisterStaticCaptureHandle(STATIC_CAPTURE_INGRESS, http.DefaultServeMux)
+
+ /*
+ Dynamic Captures
+ */
+ pathRouter.RegisterDynamicSniffHandler("/d_sniff", http.DefaultServeMux, func(dsfr *plugin.DynamicSniffForwardRequest) plugin.SniffResult {
+ fmt.Println("Dynamic Capture Sniffed Request:")
+ fmt.Println("Request URI: " + dsfr.RequestURI)
+
+ //In this example, we want to capture all URI
+ //that start with /test_ and forward it to the dynamic capture handler
+ if strings.HasPrefix(dsfr.RequestURI, "/test_") {
+ reqUUID := dsfr.GetRequestUUID()
+ fmt.Println("Accepting request with UUID: " + reqUUID)
+ return plugin.SniffResultAccpet
+ }
+
+ return plugin.SniffResultSkip
+ })
+ pathRouter.RegisterDynamicCaptureHandle("/d_capture", http.DefaultServeMux, func(w http.ResponseWriter, r *http.Request) {
+ // This is the dynamic capture handler where it actually captures and handle the request
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("Welcome to the dynamic capture handler!"))
+
+ // Print all the request info to the response writer
+ w.Write([]byte("\n\nRequest Info:\n"))
+ w.Write([]byte("Request URI: " + r.RequestURI + "\n"))
+ w.Write([]byte("Request Method: " + r.Method + "\n"))
+ w.Write([]byte("Request Headers:\n"))
+ headers := make([]string, 0, len(r.Header))
+ for key := range r.Header {
+ headers = append(headers, key)
+ }
+ sort.Strings(headers)
+ for _, key := range headers {
+ for _, value := range r.Header[key] {
+ w.Write([]byte(fmt.Sprintf("%s: %s\n", key, value)))
+ }
+ }
+ })
http.HandleFunc(UI_PATH+"/", RenderDebugUI)
fmt.Println("Debugger started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
@@ -72,21 +120,21 @@ func main() {
// Handle the captured request
func HandleCaptureA(w http.ResponseWriter, r *http.Request) {
- for key, values := range r.Header {
+ /*for key, values := range r.Header {
for _, value := range values {
fmt.Printf("%s: %s\n", key, value)
}
- }
+ }*/
w.Header().Set("Content-Type", "text/html")
w.Write([]byte("This request is captured by A handler!
Request URI: " + r.URL.String()))
}
func HandleCaptureB(w http.ResponseWriter, r *http.Request) {
- for key, values := range r.Header {
+ /*for key, values := range r.Header {
for _, value := range values {
fmt.Printf("%s: %s\n", key, value)
}
- }
+ }*/
w.Header().Set("Content-Type", "text/html")
w.Write([]byte("This request is captured by the B handler!
Request URI: " + r.URL.String()))
}
diff --git a/example/plugins/debugger/mod/zoraxy_plugin/dynamic_router.go b/example/plugins/debugger/mod/zoraxy_plugin/dynamic_router.go
new file mode 100644
index 0000000..1dc53ce
--- /dev/null
+++ b/example/plugins/debugger/mod/zoraxy_plugin/dynamic_router.go
@@ -0,0 +1,162 @@
+package zoraxy_plugin
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+/*
+
+ Dynamic Path Handler
+
+*/
+
+type SniffResult int
+
+const (
+ SniffResultAccpet SniffResult = iota // Forward the request to this plugin dynamic capture ingress
+ SniffResultSkip // Skip this plugin and let the next plugin handle the request
+)
+
+type SniffHandler func(*DynamicSniffForwardRequest) SniffResult
+
+/*
+RegisterDynamicSniffHandler registers a dynamic sniff handler for a path
+You can decide to accept or skip the request based on the request header and paths
+*/
+func (p *PathRouter) RegisterDynamicSniffHandler(sniff_ingress string, mux *http.ServeMux, handler SniffHandler) {
+ if !strings.HasSuffix(sniff_ingress, "/") {
+ sniff_ingress = sniff_ingress + "/"
+ }
+ mux.Handle(sniff_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic sniff path: " + r.RequestURI)
+ }
+
+ // Decode the request payload
+ jsonBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error reading request body:", err)
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+ payload, err := DecodeForwardRequestPayload(jsonBytes)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error decoding request payload:", err)
+ fmt.Print("Payload: ")
+ fmt.Println(string(jsonBytes))
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+
+ // Get the forwarded request UUID
+ forwardUUID := r.Header.Get("X-Zoraxy-RequestID")
+ payload.requestUUID = forwardUUID
+ payload.rawRequest = r
+
+ sniffResult := handler(&payload)
+ if sniffResult == SniffResultAccpet {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("OK"))
+ } else {
+ w.WriteHeader(http.StatusNotImplemented)
+ w.Write([]byte("SKIP"))
+ }
+ }))
+}
+
+// RegisterDynamicCaptureHandle register the dynamic capture ingress path with a handler
+func (p *PathRouter) RegisterDynamicCaptureHandle(capture_ingress string, mux *http.ServeMux, handlefunc func(http.ResponseWriter, *http.Request)) {
+ if !strings.HasSuffix(capture_ingress, "/") {
+ capture_ingress = capture_ingress + "/"
+ }
+ mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic capture path: " + r.RequestURI)
+ }
+
+ rewrittenURL := r.RequestURI
+ rewrittenURL = strings.TrimPrefix(rewrittenURL, capture_ingress)
+ rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
+ if rewrittenURL == "" {
+ rewrittenURL = "/"
+ }
+ if !strings.HasPrefix(rewrittenURL, "/") {
+ rewrittenURL = "/" + rewrittenURL
+ }
+ r.RequestURI = rewrittenURL
+
+ handlefunc(w, r)
+ }))
+}
+
+/*
+ Sniffing and forwarding
+
+ The following functions are here to help with
+ sniffing and forwarding requests to the dynamic
+ router.
+*/
+// A custom request object to be used in the dynamic sniffing
+type DynamicSniffForwardRequest struct {
+ Method string `json:"method"`
+ Hostname string `json:"hostname"`
+ URL string `json:"url"`
+ Header map[string][]string `json:"header"`
+ RemoteAddr string `json:"remote_addr"`
+ Host string `json:"host"`
+ RequestURI string `json:"request_uri"`
+ Proto string `json:"proto"`
+ ProtoMajor int `json:"proto_major"`
+ ProtoMinor int `json:"proto_minor"`
+
+ /* Internal use */
+ rawRequest *http.Request `json:"-"`
+ requestUUID string `json:"-"`
+}
+
+// GetForwardRequestPayload returns a DynamicSniffForwardRequest object from an http.Request object
+func EncodeForwardRequestPayload(r *http.Request) DynamicSniffForwardRequest {
+ return DynamicSniffForwardRequest{
+ Method: r.Method,
+ Hostname: r.Host,
+ URL: r.URL.String(),
+ Header: r.Header,
+ RemoteAddr: r.RemoteAddr,
+ Host: r.Host,
+ RequestURI: r.RequestURI,
+ Proto: r.Proto,
+ ProtoMajor: r.ProtoMajor,
+ ProtoMinor: r.ProtoMinor,
+ rawRequest: r,
+ }
+}
+
+// DecodeForwardRequestPayload decodes JSON bytes into a DynamicSniffForwardRequest object
+func DecodeForwardRequestPayload(jsonBytes []byte) (DynamicSniffForwardRequest, error) {
+ var payload DynamicSniffForwardRequest
+ err := json.Unmarshal(jsonBytes, &payload)
+ if err != nil {
+ return DynamicSniffForwardRequest{}, err
+ }
+ return payload, nil
+}
+
+// GetRequest returns the original http.Request object, for debugging purposes
+func (dsfr *DynamicSniffForwardRequest) GetRequest() *http.Request {
+ return dsfr.rawRequest
+}
+
+// GetRequestUUID returns the request UUID
+// if this UUID is empty string, that might indicate the request
+// is not coming from the dynamic router
+func (dsfr *DynamicSniffForwardRequest) GetRequestUUID() string {
+ return dsfr.requestUUID
+}
diff --git a/example/plugins/debugger/mod/zoraxy_plugin/static_router.go b/example/plugins/debugger/mod/zoraxy_plugin/static_router.go
index 13b07ff..f4abcb7 100644
--- a/example/plugins/debugger/mod/zoraxy_plugin/static_router.go
+++ b/example/plugins/debugger/mod/zoraxy_plugin/static_router.go
@@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
p.enableDebugPrint = enable
}
-func (p *PathRouter) RegisterHandle(capture_ingress string, mux *http.ServeMux) {
+// StartStaticCapture starts the static capture ingress
+func (p *PathRouter) RegisterStaticCaptureHandle(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)
+ p.staticCaptureServeHTTP(w, r)
}))
}
-func (p *PathRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+// staticCaptureServeHTTP serves the static capture path using user defined handler
+func (p *PathRouter) staticCaptureServeHTTP(w http.ResponseWriter, r *http.Request) {
capturePath := r.Header.Get("X-Zoraxy-Capture")
if capturePath != "" {
if p.enableDebugPrint {
diff --git a/example/plugins/helloworld/mod/zoraxy_plugin/dynamic_router.go b/example/plugins/helloworld/mod/zoraxy_plugin/dynamic_router.go
new file mode 100644
index 0000000..1dc53ce
--- /dev/null
+++ b/example/plugins/helloworld/mod/zoraxy_plugin/dynamic_router.go
@@ -0,0 +1,162 @@
+package zoraxy_plugin
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+/*
+
+ Dynamic Path Handler
+
+*/
+
+type SniffResult int
+
+const (
+ SniffResultAccpet SniffResult = iota // Forward the request to this plugin dynamic capture ingress
+ SniffResultSkip // Skip this plugin and let the next plugin handle the request
+)
+
+type SniffHandler func(*DynamicSniffForwardRequest) SniffResult
+
+/*
+RegisterDynamicSniffHandler registers a dynamic sniff handler for a path
+You can decide to accept or skip the request based on the request header and paths
+*/
+func (p *PathRouter) RegisterDynamicSniffHandler(sniff_ingress string, mux *http.ServeMux, handler SniffHandler) {
+ if !strings.HasSuffix(sniff_ingress, "/") {
+ sniff_ingress = sniff_ingress + "/"
+ }
+ mux.Handle(sniff_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic sniff path: " + r.RequestURI)
+ }
+
+ // Decode the request payload
+ jsonBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error reading request body:", err)
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+ payload, err := DecodeForwardRequestPayload(jsonBytes)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error decoding request payload:", err)
+ fmt.Print("Payload: ")
+ fmt.Println(string(jsonBytes))
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+
+ // Get the forwarded request UUID
+ forwardUUID := r.Header.Get("X-Zoraxy-RequestID")
+ payload.requestUUID = forwardUUID
+ payload.rawRequest = r
+
+ sniffResult := handler(&payload)
+ if sniffResult == SniffResultAccpet {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("OK"))
+ } else {
+ w.WriteHeader(http.StatusNotImplemented)
+ w.Write([]byte("SKIP"))
+ }
+ }))
+}
+
+// RegisterDynamicCaptureHandle register the dynamic capture ingress path with a handler
+func (p *PathRouter) RegisterDynamicCaptureHandle(capture_ingress string, mux *http.ServeMux, handlefunc func(http.ResponseWriter, *http.Request)) {
+ if !strings.HasSuffix(capture_ingress, "/") {
+ capture_ingress = capture_ingress + "/"
+ }
+ mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic capture path: " + r.RequestURI)
+ }
+
+ rewrittenURL := r.RequestURI
+ rewrittenURL = strings.TrimPrefix(rewrittenURL, capture_ingress)
+ rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
+ if rewrittenURL == "" {
+ rewrittenURL = "/"
+ }
+ if !strings.HasPrefix(rewrittenURL, "/") {
+ rewrittenURL = "/" + rewrittenURL
+ }
+ r.RequestURI = rewrittenURL
+
+ handlefunc(w, r)
+ }))
+}
+
+/*
+ Sniffing and forwarding
+
+ The following functions are here to help with
+ sniffing and forwarding requests to the dynamic
+ router.
+*/
+// A custom request object to be used in the dynamic sniffing
+type DynamicSniffForwardRequest struct {
+ Method string `json:"method"`
+ Hostname string `json:"hostname"`
+ URL string `json:"url"`
+ Header map[string][]string `json:"header"`
+ RemoteAddr string `json:"remote_addr"`
+ Host string `json:"host"`
+ RequestURI string `json:"request_uri"`
+ Proto string `json:"proto"`
+ ProtoMajor int `json:"proto_major"`
+ ProtoMinor int `json:"proto_minor"`
+
+ /* Internal use */
+ rawRequest *http.Request `json:"-"`
+ requestUUID string `json:"-"`
+}
+
+// GetForwardRequestPayload returns a DynamicSniffForwardRequest object from an http.Request object
+func EncodeForwardRequestPayload(r *http.Request) DynamicSniffForwardRequest {
+ return DynamicSniffForwardRequest{
+ Method: r.Method,
+ Hostname: r.Host,
+ URL: r.URL.String(),
+ Header: r.Header,
+ RemoteAddr: r.RemoteAddr,
+ Host: r.Host,
+ RequestURI: r.RequestURI,
+ Proto: r.Proto,
+ ProtoMajor: r.ProtoMajor,
+ ProtoMinor: r.ProtoMinor,
+ rawRequest: r,
+ }
+}
+
+// DecodeForwardRequestPayload decodes JSON bytes into a DynamicSniffForwardRequest object
+func DecodeForwardRequestPayload(jsonBytes []byte) (DynamicSniffForwardRequest, error) {
+ var payload DynamicSniffForwardRequest
+ err := json.Unmarshal(jsonBytes, &payload)
+ if err != nil {
+ return DynamicSniffForwardRequest{}, err
+ }
+ return payload, nil
+}
+
+// GetRequest returns the original http.Request object, for debugging purposes
+func (dsfr *DynamicSniffForwardRequest) GetRequest() *http.Request {
+ return dsfr.rawRequest
+}
+
+// GetRequestUUID returns the request UUID
+// if this UUID is empty string, that might indicate the request
+// is not coming from the dynamic router
+func (dsfr *DynamicSniffForwardRequest) GetRequestUUID() string {
+ return dsfr.requestUUID
+}
diff --git a/example/plugins/helloworld/mod/zoraxy_plugin/static_router.go b/example/plugins/helloworld/mod/zoraxy_plugin/static_router.go
index 13b07ff..f4abcb7 100644
--- a/example/plugins/helloworld/mod/zoraxy_plugin/static_router.go
+++ b/example/plugins/helloworld/mod/zoraxy_plugin/static_router.go
@@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
p.enableDebugPrint = enable
}
-func (p *PathRouter) RegisterHandle(capture_ingress string, mux *http.ServeMux) {
+// StartStaticCapture starts the static capture ingress
+func (p *PathRouter) RegisterStaticCaptureHandle(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)
+ p.staticCaptureServeHTTP(w, r)
}))
}
-func (p *PathRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+// staticCaptureServeHTTP serves the static capture path using user defined handler
+func (p *PathRouter) staticCaptureServeHTTP(w http.ResponseWriter, r *http.Request) {
capturePath := r.Header.Get("X-Zoraxy-Capture")
if capturePath != "" {
if p.enableDebugPrint {
diff --git a/example/plugins/static_capture/go.mod b/example/plugins/static_capture/go.mod
deleted file mode 100644
index 25a7842..0000000
--- a/example/plugins/static_capture/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module aroz.org/zoraxy/example/static_capture
-
-go 1.23.6
diff --git a/example/plugins/static_capture/main.go b/example/plugins/static_capture/main.go
deleted file mode 100644
index 09ac268..0000000
--- a/example/plugins/static_capture/main.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package main
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- plugin "aroz.org/zoraxy/example/static_capture/mod/zoraxy_plugin"
-)
-
-const (
- PLUGIN_ID = "org.aroz.zoraxy.static_capture"
- UI_PATH = "/ui"
- STATIC_CAPTURE_INGRESS = "/static_capture"
-)
-
-func main() {
- // Serve the plugin intro spect
- // This will print the plugin intro spect and exit if the -introspect flag is provided
- runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
- ID: PLUGIN_ID,
- Name: "Static Capture Example",
- Author: "aroz.org",
- AuthorContact: "https://aroz.org",
- Description: "An example plugin implementing static capture",
- URL: "https://zoraxy.aroz.org",
- Type: plugin.PluginType_Router,
- VersionMajor: 1,
- VersionMinor: 0,
- VersionPatch: 0,
-
- /*
- 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
-
- In this example, we will capture two paths
- /test_a and /test_b. Once this plugin is enabled on a HTTP proxy rule, let say
- https://example.com the plugin will capture all requests to
- https://example.com/test_a and https://example.com/test_b
- and reverse proxy it to the StaticCaptureIngress path of your plugin like
- /static_capture/test_a and /static_capture/test_b
- */
- StaticCapturePaths: []plugin.StaticCaptureRule{
- {
- CapturePath: "/test_a/",
- },
- {
- CapturePath: "/test_b/",
- },
- },
- StaticCaptureIngress: STATIC_CAPTURE_INGRESS,
-
- UIPath: UI_PATH,
- })
- if err != nil {
- //Terminate or enter standalone mode here
- panic(err)
- }
-
- /*
- Static Capture Router
-
- The plugin library already provided a path router to handle the static capture
- paths. The path router will capture the requests to the specified paths and
- restore the original request path before forwarding it to the handler.
-
- In this example, we will create a path router to handle the two capture paths
- /test_a and /test_b. The path router will capture the requests to these paths
- and print the request headers to the console.
- */
- pathRouter := plugin.NewPathRouter()
- pathRouter.SetDebugPrintMode(true)
- pathRouter.RegisterPathHandler("/test_a/", http.HandlerFunc(HandleCaptureA))
- pathRouter.RegisterPathHandler("/test_b/", http.HandlerFunc(HandleCaptureB))
- pathRouter.SetDefaultHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- //In theory this should never be called
- //but just in case the request is not captured by the path handlers
- //like if you have forgotten to register the handler for the capture path
- w.Header().Set("Content-Type", "text/html")
- w.Write([]byte("This request is captured by the default handler!
Request URI: " + r.URL.String()))
- }))
- //Lastly, register the path routing to the default http mux (or the mux you are using)
- pathRouter.RegisterHandle(STATIC_CAPTURE_INGRESS, http.DefaultServeMux)
-
- //Create a path handler for the UI
- http.HandleFunc(UI_PATH+"/", RenderDebugUI)
- fmt.Println("Debugger started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
- http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
-}
-
-// Handle the captured request
-func HandleCaptureA(w http.ResponseWriter, r *http.Request) {
- fmt.Println("Request captured by A handler")
- w.Header().Set("Content-Type", "text/html")
- w.Write([]byte("This request is captured by A handler!
Request URI: " + r.URL.String()))
-}
-
-func HandleCaptureB(w http.ResponseWriter, r *http.Request) {
- fmt.Println("Request captured by B handler")
- w.Header().Set("Content-Type", "text/html")
- w.Write([]byte("This request is captured by the B handler!
Request URI: " + r.URL.String()))
-}
diff --git a/example/plugins/static_capture/mod/zoraxy_plugin/README.txt b/example/plugins/static_capture/mod/zoraxy_plugin/README.txt
deleted file mode 100644
index ed8a405..0000000
--- a/example/plugins/static_capture/mod/zoraxy_plugin/README.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-# Zoraxy Plugin
-
-## Overview
-This module serves as a template for building your own plugins for the Zoraxy Reverse Proxy. By copying this module to your plugin mod folder, you can create a new plugin with the necessary structure and components.
-
-## Instructions
-
-1. **Copy the Module:**
- - Copy the entire `zoraxy_plugin` module to your plugin mod folder.
-
-2. **Include the Structure:**
- - Ensure that you maintain the directory structure and file organization as provided in this module.
-
-3. **Modify as Needed:**
- - Customize the copied module to implement the desired functionality for your plugin.
-
-## Directory Structure
- zoraxy_plugin: Handle -introspect and -configuration process required for plugin loading and startup
- embed_webserver: Handle embeded web server routing and injecting csrf token to your plugin served UI pages
\ No newline at end of file
diff --git a/example/plugins/static_capture/mod/zoraxy_plugin/embed_webserver.go b/example/plugins/static_capture/mod/zoraxy_plugin/embed_webserver.go
deleted file mode 100644
index c529e99..0000000
--- a/example/plugins/static_capture/mod/zoraxy_plugin/embed_webserver.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package zoraxy_plugin
-
-import (
- "embed"
- "fmt"
- "io/fs"
- "net/http"
- "net/url"
- "os"
- "strings"
- "time"
-)
-
-type PluginUiRouter struct {
- PluginID string //The ID of the plugin
- TargetFs *embed.FS //The embed.FS where the UI files are stored
- TargetFsPrefix string //The prefix of the embed.FS where the UI files are stored, e.g. /web
- HandlerPrefix string //The prefix of the handler used to route this router, e.g. /ui
-
- terminateHandler func() //The handler to be called when the plugin is terminated
-}
-
-// NewPluginEmbedUIRouter creates a new PluginUiRouter with embed.FS
-// The targetFsPrefix is the prefix of the embed.FS where the UI files are stored
-// The targetFsPrefix should be relative to the root of the embed.FS
-// The targetFsPrefix should start with a slash (e.g. /web) that corresponds to the root folder of the embed.FS
-// The handlerPrefix is the prefix of the handler used to route this router
-// The handlerPrefix should start with a slash (e.g. /ui) that matches the http.Handle path
-// All prefix should not end with a slash
-func NewPluginEmbedUIRouter(pluginID string, targetFs *embed.FS, targetFsPrefix string, handlerPrefix string) *PluginUiRouter {
- //Make sure all prefix are in /prefix format
- if !strings.HasPrefix(targetFsPrefix, "/") {
- targetFsPrefix = "/" + targetFsPrefix
- }
- targetFsPrefix = strings.TrimSuffix(targetFsPrefix, "/")
-
- if !strings.HasPrefix(handlerPrefix, "/") {
- handlerPrefix = "/" + handlerPrefix
- }
- handlerPrefix = strings.TrimSuffix(handlerPrefix, "/")
-
- //Return the PluginUiRouter
- return &PluginUiRouter{
- PluginID: pluginID,
- TargetFs: targetFs,
- TargetFsPrefix: targetFsPrefix,
- HandlerPrefix: handlerPrefix,
- }
-}
-
-func (p *PluginUiRouter) populateCSRFToken(r *http.Request, fsHandler http.Handler) http.Handler {
- //Get the CSRF token from header
- csrfToken := r.Header.Get("X-Zoraxy-Csrf")
- if csrfToken == "" {
- csrfToken = "missing-csrf-token"
- }
-
- //Return the middleware
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- // Check if the request is for an HTML file
- if strings.HasSuffix(r.URL.Path, "/") {
- // Redirect to the index.html
- http.Redirect(w, r, r.URL.Path+"index.html", http.StatusFound)
- return
- }
- if strings.HasSuffix(r.URL.Path, ".html") {
- //Read the target file from embed.FS
- targetFilePath := strings.TrimPrefix(r.URL.Path, "/")
- targetFilePath = p.TargetFsPrefix + "/" + targetFilePath
- targetFilePath = strings.TrimPrefix(targetFilePath, "/")
- targetFileContent, err := fs.ReadFile(*p.TargetFs, targetFilePath)
- if err != nil {
- http.Error(w, "File not found", http.StatusNotFound)
- return
- }
- body := string(targetFileContent)
- body = strings.ReplaceAll(body, "{{.csrfToken}}", csrfToken)
- http.ServeContent(w, r, r.URL.Path, time.Now(), strings.NewReader(body))
- return
- }
-
- //Call the next handler
- fsHandler.ServeHTTP(w, r)
- })
-
-}
-
-// GetHttpHandler returns the http.Handler for the PluginUiRouter
-func (p *PluginUiRouter) Handler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- //Remove the plugin UI handler path prefix
- rewrittenURL := r.RequestURI
- rewrittenURL = strings.TrimPrefix(rewrittenURL, p.HandlerPrefix)
- rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
- r.URL, _ = url.Parse(rewrittenURL)
- r.RequestURI = rewrittenURL
-
- //Serve the file from the embed.FS
- subFS, err := fs.Sub(*p.TargetFs, strings.TrimPrefix(p.TargetFsPrefix, "/"))
- if err != nil {
- fmt.Println(err.Error())
- http.Error(w, "Internal Server Error", http.StatusInternalServerError)
- return
- }
-
- // Replace {{csrf_token}} with the actual CSRF token and serve the file
- p.populateCSRFToken(r, http.FileServer(http.FS(subFS))).ServeHTTP(w, r)
- })
-}
-
-// RegisterTerminateHandler registers the terminate handler for the PluginUiRouter
-// The terminate handler will be called when the plugin is terminated from Zoraxy plugin manager
-// if mux is nil, the handler will be registered to http.DefaultServeMux
-func (p *PluginUiRouter) RegisterTerminateHandler(termFunc func(), mux *http.ServeMux) {
- p.terminateHandler = termFunc
- if mux == nil {
- mux = http.DefaultServeMux
- }
- mux.HandleFunc(p.HandlerPrefix+"/term", func(w http.ResponseWriter, r *http.Request) {
- p.terminateHandler()
- w.WriteHeader(http.StatusOK)
- go func() {
- //Make sure the response is sent before the plugin is terminated
- time.Sleep(100 * time.Millisecond)
- os.Exit(0)
- }()
- })
-}
diff --git a/example/plugins/static_capture/mod/zoraxy_plugin/static_router.go b/example/plugins/static_capture/mod/zoraxy_plugin/static_router.go
deleted file mode 100644
index 13b07ff..0000000
--- a/example/plugins/static_capture/mod/zoraxy_plugin/static_router.go
+++ /dev/null
@@ -1,103 +0,0 @@
-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)
-
- }
-}
diff --git a/example/plugins/static_capture/mod/zoraxy_plugin/zoraxy_plugin.go b/example/plugins/static_capture/mod/zoraxy_plugin/zoraxy_plugin.go
deleted file mode 100644
index 2cf494e..0000000
--- a/example/plugins/static_capture/mod/zoraxy_plugin/zoraxy_plugin.go
+++ /dev/null
@@ -1,175 +0,0 @@
-package zoraxy_plugin
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "strings"
-)
-
-/*
- Plugins Includes.go
-
- This file is copied from Zoraxy source code
- You can always find the latest version under mod/plugins/includes.go
- Usually this file are backward compatible
-*/
-
-type PluginType int
-
-const (
- PluginType_Router PluginType = 0 //Router Plugin, used for handling / routing / forwarding traffic
- PluginType_Utilities PluginType = 1 //Utilities Plugin, used for utilities like Zerotier or Static Web Server that do not require interception with the dpcore
-)
-
-type StaticCaptureRule struct {
- CapturePath string `json:"capture_path"`
- //To be expanded
-}
-
-type ControlStatusCode int
-
-const (
- ControlStatusCode_CAPTURED ControlStatusCode = 280 //Traffic captured by plugin, ask Zoraxy not to process the traffic
- ControlStatusCode_UNHANDLED ControlStatusCode = 284 //Traffic not handled by plugin, ask Zoraxy to process the traffic
- ControlStatusCode_ERROR ControlStatusCode = 580 //Error occurred while processing the traffic, ask Zoraxy to process the traffic and log the error
-)
-
-type SubscriptionEvent struct {
- EventName string `json:"event_name"`
- EventSource string `json:"event_source"`
- Payload string `json:"payload"` //Payload of the event, can be empty
-}
-
-type RuntimeConstantValue struct {
- ZoraxyVersion string `json:"zoraxy_version"`
- ZoraxyUUID string `json:"zoraxy_uuid"`
-}
-
-/*
-IntroSpect Payload
-
-When the plugin is initialized with -introspect flag,
-the plugin shell return this payload as JSON and exit
-*/
-type IntroSpect struct {
- /* Plugin metadata */
- ID string `json:"id"` //Unique ID of your plugin, recommended using your own domain in reverse like com.yourdomain.pluginname
- Name string `json:"name"` //Name of your plugin
- Author string `json:"author"` //Author name of your plugin
- AuthorContact string `json:"author_contact"` //Author contact of your plugin, like email
- Description string `json:"description"` //Description of your plugin
- URL string `json:"url"` //URL of your plugin
- Type PluginType `json:"type"` //Type of your plugin, Router(0) or Utilities(1)
- VersionMajor int `json:"version_major"` //Major version of your plugin
- VersionMinor int `json:"version_minor"` //Minor version of your plugin
- VersionPatch int `json:"version_patch"` //Patch version of your plugin
-
- /*
-
- Endpoint Settings
-
- */
-
- /*
- 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
- */
- 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)
-
- /*
- Dynamic Capture Settings
-
- 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
- */
- 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
-
- /* Subscriptions Settings */
- SubscriptionPath string `json:"subscription_path"` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered
- SubscriptionsEvents map[string]string `json:"subscriptions_events"` //Subscriptions events of your plugin, see Zoraxy documentation for more details
-}
-
-/*
-ServeIntroSpect Function
-
-This function will check if the plugin is initialized with -introspect flag,
-if so, it will print the intro spect and exit
-
-Place this function at the beginning of your plugin main function
-*/
-func ServeIntroSpect(pluginSpect *IntroSpect) {
- if len(os.Args) > 1 && os.Args[1] == "-introspect" {
- //Print the intro spect and exit
- jsonData, _ := json.MarshalIndent(pluginSpect, "", " ")
- fmt.Println(string(jsonData))
- os.Exit(0)
- }
-}
-
-/*
-ConfigureSpec Payload
-
-Zoraxy will start your plugin with -configure flag,
-the plugin shell read this payload as JSON and configure itself
-by the supplied values like starting a web server at given port
-that listens to 127.0.0.1:port
-*/
-type ConfigureSpec struct {
- Port int `json:"port"` //Port to listen
- RuntimeConst RuntimeConstantValue `json:"runtime_const"` //Runtime constant values
- //To be expanded
-}
-
-/*
-RecvExecuteConfigureSpec Function
-
-This function will read the configure spec from Zoraxy
-and return the ConfigureSpec object
-
-Place this function after ServeIntroSpect function in your plugin main function
-*/
-func RecvConfigureSpec() (*ConfigureSpec, error) {
- for i, arg := range os.Args {
- if strings.HasPrefix(arg, "-configure=") {
- var configSpec ConfigureSpec
- if err := json.Unmarshal([]byte(arg[11:]), &configSpec); err != nil {
- return nil, err
- }
- return &configSpec, nil
- } else if arg == "-configure" {
- var configSpec ConfigureSpec
- var nextArg string
- if len(os.Args) > i+1 {
- nextArg = os.Args[i+1]
- if err := json.Unmarshal([]byte(nextArg), &configSpec); err != nil {
- return nil, err
- }
- } else {
- return nil, fmt.Errorf("No port specified after -configure flag")
- }
- return &configSpec, nil
- }
- }
- return nil, fmt.Errorf("No -configure flag found")
-}
-
-/*
-ServeAndRecvSpec Function
-
-This function will serve the intro spect and return the configure spec
-See the ServeIntroSpect and RecvConfigureSpec for more details
-*/
-func ServeAndRecvSpec(pluginSpect *IntroSpect) (*ConfigureSpec, error) {
- ServeIntroSpect(pluginSpect)
- return RecvConfigureSpec()
-}
diff --git a/example/plugins/static_capture/ui_info.go b/example/plugins/static_capture/ui_info.go
deleted file mode 100644
index 18c307b..0000000
--- a/example/plugins/static_capture/ui_info.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package main
-
-import (
- _ "embed"
- "fmt"
- "net/http"
- "sort"
-)
-
-// Render the debug UI
-func RenderDebugUI(w http.ResponseWriter, r *http.Request) {
- fmt.Fprint(w, "**static_capture Debug Interface**\n\n[Recv Headers] \n")
-
- headerKeys := make([]string, 0, len(r.Header))
- for name := range r.Header {
- headerKeys = append(headerKeys, name)
- }
- sort.Strings(headerKeys)
- for _, name := range headerKeys {
- values := r.Header[name]
- for _, value := range values {
- fmt.Fprintf(w, "%s: %s\n", name, value)
- }
- }
- w.Header().Set("Content-Type", "text/html")
-}
diff --git a/example/plugins/ztnc/mod/zoraxy_plugin/dynamic_router.go b/example/plugins/ztnc/mod/zoraxy_plugin/dynamic_router.go
new file mode 100644
index 0000000..1dc53ce
--- /dev/null
+++ b/example/plugins/ztnc/mod/zoraxy_plugin/dynamic_router.go
@@ -0,0 +1,162 @@
+package zoraxy_plugin
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+/*
+
+ Dynamic Path Handler
+
+*/
+
+type SniffResult int
+
+const (
+ SniffResultAccpet SniffResult = iota // Forward the request to this plugin dynamic capture ingress
+ SniffResultSkip // Skip this plugin and let the next plugin handle the request
+)
+
+type SniffHandler func(*DynamicSniffForwardRequest) SniffResult
+
+/*
+RegisterDynamicSniffHandler registers a dynamic sniff handler for a path
+You can decide to accept or skip the request based on the request header and paths
+*/
+func (p *PathRouter) RegisterDynamicSniffHandler(sniff_ingress string, mux *http.ServeMux, handler SniffHandler) {
+ if !strings.HasSuffix(sniff_ingress, "/") {
+ sniff_ingress = sniff_ingress + "/"
+ }
+ mux.Handle(sniff_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic sniff path: " + r.RequestURI)
+ }
+
+ // Decode the request payload
+ jsonBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error reading request body:", err)
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+ payload, err := DecodeForwardRequestPayload(jsonBytes)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error decoding request payload:", err)
+ fmt.Print("Payload: ")
+ fmt.Println(string(jsonBytes))
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+
+ // Get the forwarded request UUID
+ forwardUUID := r.Header.Get("X-Zoraxy-RequestID")
+ payload.requestUUID = forwardUUID
+ payload.rawRequest = r
+
+ sniffResult := handler(&payload)
+ if sniffResult == SniffResultAccpet {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("OK"))
+ } else {
+ w.WriteHeader(http.StatusNotImplemented)
+ w.Write([]byte("SKIP"))
+ }
+ }))
+}
+
+// RegisterDynamicCaptureHandle register the dynamic capture ingress path with a handler
+func (p *PathRouter) RegisterDynamicCaptureHandle(capture_ingress string, mux *http.ServeMux, handlefunc func(http.ResponseWriter, *http.Request)) {
+ if !strings.HasSuffix(capture_ingress, "/") {
+ capture_ingress = capture_ingress + "/"
+ }
+ mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic capture path: " + r.RequestURI)
+ }
+
+ rewrittenURL := r.RequestURI
+ rewrittenURL = strings.TrimPrefix(rewrittenURL, capture_ingress)
+ rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
+ if rewrittenURL == "" {
+ rewrittenURL = "/"
+ }
+ if !strings.HasPrefix(rewrittenURL, "/") {
+ rewrittenURL = "/" + rewrittenURL
+ }
+ r.RequestURI = rewrittenURL
+
+ handlefunc(w, r)
+ }))
+}
+
+/*
+ Sniffing and forwarding
+
+ The following functions are here to help with
+ sniffing and forwarding requests to the dynamic
+ router.
+*/
+// A custom request object to be used in the dynamic sniffing
+type DynamicSniffForwardRequest struct {
+ Method string `json:"method"`
+ Hostname string `json:"hostname"`
+ URL string `json:"url"`
+ Header map[string][]string `json:"header"`
+ RemoteAddr string `json:"remote_addr"`
+ Host string `json:"host"`
+ RequestURI string `json:"request_uri"`
+ Proto string `json:"proto"`
+ ProtoMajor int `json:"proto_major"`
+ ProtoMinor int `json:"proto_minor"`
+
+ /* Internal use */
+ rawRequest *http.Request `json:"-"`
+ requestUUID string `json:"-"`
+}
+
+// GetForwardRequestPayload returns a DynamicSniffForwardRequest object from an http.Request object
+func EncodeForwardRequestPayload(r *http.Request) DynamicSniffForwardRequest {
+ return DynamicSniffForwardRequest{
+ Method: r.Method,
+ Hostname: r.Host,
+ URL: r.URL.String(),
+ Header: r.Header,
+ RemoteAddr: r.RemoteAddr,
+ Host: r.Host,
+ RequestURI: r.RequestURI,
+ Proto: r.Proto,
+ ProtoMajor: r.ProtoMajor,
+ ProtoMinor: r.ProtoMinor,
+ rawRequest: r,
+ }
+}
+
+// DecodeForwardRequestPayload decodes JSON bytes into a DynamicSniffForwardRequest object
+func DecodeForwardRequestPayload(jsonBytes []byte) (DynamicSniffForwardRequest, error) {
+ var payload DynamicSniffForwardRequest
+ err := json.Unmarshal(jsonBytes, &payload)
+ if err != nil {
+ return DynamicSniffForwardRequest{}, err
+ }
+ return payload, nil
+}
+
+// GetRequest returns the original http.Request object, for debugging purposes
+func (dsfr *DynamicSniffForwardRequest) GetRequest() *http.Request {
+ return dsfr.rawRequest
+}
+
+// GetRequestUUID returns the request UUID
+// if this UUID is empty string, that might indicate the request
+// is not coming from the dynamic router
+func (dsfr *DynamicSniffForwardRequest) GetRequestUUID() string {
+ return dsfr.requestUUID
+}
diff --git a/example/plugins/ztnc/mod/zoraxy_plugin/static_router.go b/example/plugins/ztnc/mod/zoraxy_plugin/static_router.go
index 13b07ff..f4abcb7 100644
--- a/example/plugins/ztnc/mod/zoraxy_plugin/static_router.go
+++ b/example/plugins/ztnc/mod/zoraxy_plugin/static_router.go
@@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
p.enableDebugPrint = enable
}
-func (p *PathRouter) RegisterHandle(capture_ingress string, mux *http.ServeMux) {
+// StartStaticCapture starts the static capture ingress
+func (p *PathRouter) RegisterStaticCaptureHandle(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)
+ p.staticCaptureServeHTTP(w, r)
}))
}
-func (p *PathRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+// staticCaptureServeHTTP serves the static capture path using user defined handler
+func (p *PathRouter) staticCaptureServeHTTP(w http.ResponseWriter, r *http.Request) {
capturePath := r.Header.Get("X-Zoraxy-Capture")
if capturePath != "" {
if p.enableDebugPrint {
diff --git a/src/mod/plugins/dynamic_forwarder.go b/src/mod/plugins/dynamic_forwarder.go
new file mode 100644
index 0000000..54fea8c
--- /dev/null
+++ b/src/mod/plugins/dynamic_forwarder.go
@@ -0,0 +1,111 @@
+package plugins
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/google/uuid"
+ "imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
+ "imuslab.com/zoraxy/mod/plugins/zoraxy_plugin"
+)
+
+// StartDynamicForwardRouter create and start a dynamic forward router for
+// this plugin
+func (p *Plugin) StartDynamicForwardRouter() error {
+ // Create a new dpcore object to forward the traffic to the plugin
+ targetURL, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(p.AssignedPort) + p.Spec.DynamicCaptureIngress)
+ if err != nil {
+ fmt.Println("Failed to parse target URL: "+targetURL.String(), err)
+ return err
+ }
+ thisRouter := dpcore.NewDynamicProxyCore(targetURL, "", &dpcore.DpcoreOptions{})
+ p.dynamicRouteProxy = thisRouter
+ return nil
+}
+
+// StopDynamicForwardRouter stops the dynamic forward router for this plugin
+func (p *Plugin) StopDynamicForwardRouter() {
+ if p.dynamicRouteProxy != nil {
+ p.dynamicRouteProxy = nil
+ }
+}
+
+// AcceptDynamicRoute returns whether this plugin accepts dynamic route
+func (p *Plugin) AcceptDynamicRoute() bool {
+ return p.Spec.DynamicCaptureSniff != "" && p.Spec.DynamicCaptureIngress != ""
+}
+
+func (p *Plugin) HandleDynamicRoute(w http.ResponseWriter, r *http.Request) bool {
+ //Make sure p.Spec.DynamicCaptureSniff and p.Spec.DynamicCaptureIngress are not empty and start with /
+ if !p.AcceptDynamicRoute() {
+ return false
+ }
+
+ //Make sure the paths start with / and do not end with /
+ if !strings.HasPrefix(p.Spec.DynamicCaptureSniff, "/") {
+ p.Spec.DynamicCaptureSniff = "/" + p.Spec.DynamicCaptureSniff
+ }
+ p.Spec.DynamicCaptureSniff = strings.TrimSuffix(p.Spec.DynamicCaptureSniff, "/")
+ if !strings.HasPrefix(p.Spec.DynamicCaptureIngress, "/") {
+ p.Spec.DynamicCaptureIngress = "/" + p.Spec.DynamicCaptureIngress
+ }
+ p.Spec.DynamicCaptureIngress = strings.TrimSuffix(p.Spec.DynamicCaptureIngress, "/")
+
+ //Send the request to the sniff endpoint
+ sniffURL, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(p.AssignedPort) + p.Spec.DynamicCaptureSniff + "/")
+ if err != nil {
+ //Error when parsing the sniff URL, let the next plugin handle the request
+ return false
+ }
+
+ // Create an instance of CustomRequest with the original request's data
+ forwardReq := zoraxy_plugin.EncodeForwardRequestPayload(r)
+
+ // Encode the custom request object into JSON
+ jsonData, err := json.Marshal(forwardReq)
+ if err != nil {
+ // Error when encoding the request, let the next plugin handle the request
+ return false
+ }
+
+ //Generate a unique request ID
+ uniqueRequestID := uuid.New().String()
+
+ req, err := http.NewRequest("POST", sniffURL.String(), bytes.NewBuffer(jsonData))
+ if err != nil {
+ // Error when creating the request, let the next plugin handle the request
+ return false
+ }
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("X-Zoraxy-RequestID", uniqueRequestID)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ // Error when sending the request, let the next plugin handle the request
+ return false
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ // Sniff endpoint did not return OK, let the next plugin handle the request
+ return false
+ }
+
+ p.dynamicRouteProxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
+ UseTLS: false,
+ OriginalHost: r.Host,
+ ProxyDomain: "127.0.0.1:" + strconv.Itoa(p.AssignedPort),
+ NoCache: true,
+ PathPrefix: p.Spec.DynamicCaptureIngress,
+ UpstreamHeaders: [][]string{
+ {"X-Zoraxy-RequestID", uniqueRequestID},
+ },
+ })
+ return true
+}
diff --git a/src/mod/plugins/lifecycle.go b/src/mod/plugins/lifecycle.go
index e2462e8..ce964a5 100644
--- a/src/mod/plugins/lifecycle.go
+++ b/src/mod/plugins/lifecycle.go
@@ -46,6 +46,7 @@ func (m *Manager) StartPlugin(pluginID string) error {
}
js, _ := json.Marshal(pluginConfiguration)
+ //Start the plugin with given configuration
m.Log("Starting plugin "+thisPlugin.Spec.Name+" at :"+strconv.Itoa(pluginConfiguration.Port), nil)
cmd := exec.Command(absolutePath, "-configure="+string(js))
cmd.Dir = filepath.Dir(absolutePath)
@@ -95,6 +96,12 @@ func (m *Manager) StartPlugin(pluginID string) error {
//Create a new static forwarder router for each of the static capture paths
plugin.(*Plugin).StartAllStaticPathRouters()
+
+ //If the plugin contains dynamic capture, create a dynamic capture handler
+ if thisPlugin.AcceptDynamicRoute() {
+ plugin.(*Plugin).StartDynamicForwardRouter()
+ }
+
return nil
}
@@ -202,6 +209,7 @@ func (m *Manager) StopPlugin(pluginID string) error {
thisPlugin.uiProxy = nil
plugin.(*Plugin).Enabled = false
plugin.(*Plugin).StopAllStaticPathRouters()
+ plugin.(*Plugin).StopDynamicForwardRouter()
return nil
}
diff --git a/src/mod/plugins/plugins.go b/src/mod/plugins/plugins.go
index 21799b6..eb2c8e7 100644
--- a/src/mod/plugins/plugins.go
+++ b/src/mod/plugins/plugins.go
@@ -39,7 +39,8 @@ func NewPluginManager(options *ManagerOptions) *Manager {
return &Manager{
LoadedPlugins: sync.Map{},
- TagPluginMap: sync.Map{},
+ tagPluginMap: sync.Map{},
+ tagPluginList: make(map[string][]*Plugin),
Options: options,
}
}
@@ -76,7 +77,7 @@ func (m *Manager) LoadPluginsFromDisk() error {
}
//Generate the static forwarder radix tree
- m.UpdateTagsToTree()
+ m.UpdateTagsToPluginMaps()
return nil
}
@@ -98,7 +99,7 @@ func (m *Manager) EnablePlugin(pluginID string) error {
}
m.Options.Database.Write("plugins", pluginID, true)
//Generate the static forwarder radix tree
- m.UpdateTagsToTree()
+ m.UpdateTagsToPluginMaps()
return nil
}
@@ -110,7 +111,7 @@ func (m *Manager) DisablePlugin(pluginID string) error {
return err
}
//Generate the static forwarder radix tree
- m.UpdateTagsToTree()
+ m.UpdateTagsToPluginMaps()
return nil
}
@@ -184,11 +185,17 @@ func (m *Plugin) StartAllStaticPathRouters() {
}
}
+// StopAllStaticPathRouters stops all static path routers
func (m *Plugin) StopAllStaticPathRouters() {
-
+ for path := range m.staticRouteProxy {
+ m.staticRouteProxy[path] = nil
+ delete(m.staticRouteProxy, path)
+ }
+ m.staticRouteProxy = make(map[string]*dpcore.ReverseProxy)
}
-func (p *Plugin) HandleRoute(w http.ResponseWriter, r *http.Request, longestPrefix string) {
+// HandleStaticRoute handles the request to the plugin via static path captures (static forwarder)
+func (p *Plugin) HandleStaticRoute(w http.ResponseWriter, r *http.Request, longestPrefix string) {
longestPrefix = strings.TrimSuffix(longestPrefix, "/")
targetRouter := p.staticRouteProxy[longestPrefix]
if targetRouter == nil {
diff --git a/src/mod/plugins/static_forwarder.go b/src/mod/plugins/static_forwarder.go
index 0f33c06..3acd286 100644
--- a/src/mod/plugins/static_forwarder.go
+++ b/src/mod/plugins/static_forwarder.go
@@ -15,12 +15,25 @@ import (
request path registered when the plugin started
*/
-func (m *Manager) UpdateTagsToTree() {
+func (m *Manager) UpdateTagsToPluginMaps() {
//build the tag to plugin pointer sync.Map
- m.TagPluginMap = sync.Map{}
+ m.tagPluginMap = sync.Map{}
for tag, pluginIds := range m.Options.PluginGroups {
tree := m.GetForwarderRadixTreeFromPlugins(pluginIds)
- m.TagPluginMap.Store(tag, tree)
+ m.tagPluginMap.Store(tag, tree)
+ }
+
+ //build the plugin list for each tag
+ m.tagPluginList = make(map[string][]*Plugin)
+ for tag, pluginIds := range m.Options.PluginGroups {
+ for _, pluginId := range pluginIds {
+ plugin, err := m.GetPluginByID(pluginId)
+ if err != nil {
+ m.Log("Failed to get plugin by ID: "+pluginId, err)
+ continue
+ }
+ m.tagPluginList[tag] = append(m.tagPluginList[tag], plugin)
+ }
}
}
@@ -61,8 +74,6 @@ func (m *Manager) GetForwarderRadixTreeFromPlugins(pluginIds []string) *radix.Tr
} 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 {
diff --git a/src/mod/plugins/static_router.go b/src/mod/plugins/static_router.go
deleted file mode 100644
index 0417389..0000000
--- a/src/mod/plugins/static_router.go
+++ /dev/null
@@ -1,47 +0,0 @@
-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
-}
diff --git a/src/mod/plugins/traffic_router.go b/src/mod/plugins/traffic_router.go
new file mode 100644
index 0000000..b408a61
--- /dev/null
+++ b/src/mod/plugins/traffic_router.go
@@ -0,0 +1,73 @@
+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
+ wg := sync.WaitGroup{} //Wait group for the goroutines
+ mutex := sync.Mutex{} //Mutex for the dynamic route handler
+ var staticRoutehandlers []*Plugin //The handler for the request, can be multiple plugins
+ var longestPrefixAcrossAlltags string = "" //The longest prefix across all tags
+ var dynamicRouteHandlers []*Plugin //The handler for the dynamic routes
+ 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
+ staticRoutehandlers = pluginList.([]*Plugin)
+ }
+ }
+
+ }(tag)
+
+ //Check if the plugin enabled dynamic route
+ wg.Add(1)
+ go func(thisTag string) {
+ defer wg.Done()
+ for _, plugin := range m.tagPluginList[thisTag] {
+ if plugin.Enabled && plugin.Spec.DynamicCaptureSniff != "" && plugin.Spec.DynamicCaptureIngress != "" {
+ mutex.Lock()
+ dynamicRouteHandlers = append(dynamicRouteHandlers, plugin)
+ mutex.Unlock()
+ }
+ }
+ }(tag)
+ }
+ wg.Wait()
+
+ //Handle the static route if found
+ if len(staticRoutehandlers) > 0 {
+ //Handle the request
+ staticRoutehandlers[0].HandleStaticRoute(w, r, longestPrefixAcrossAlltags)
+ return true
+ }
+
+ //No static route handler found, check for dynamic route handler
+ for _, plugin := range dynamicRouteHandlers {
+ if plugin.HandleDynamicRoute(w, r) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/mod/plugins/typdef.go b/src/mod/plugins/typdef.go
index d99caa4..8c27721 100644
--- a/src/mod/plugins/typdef.go
+++ b/src/mod/plugins/typdef.go
@@ -21,23 +21,27 @@ 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
- staticRouteProxy map[string]*dpcore.ReverseProxy //Storing longest prefix => dpcore map for static route
- 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
+ dynamicRouteProxy *dpcore.ReverseProxy //The reverse proxy for the dynamic route
+ process *exec.Cmd //The process of the plugin
}
type ManagerOptions struct {
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
+
+ /* Runtime */
SystemConst *zoraxyPlugin.RuntimeConstantValue
- Database *database.Database
- Logger *logger.Logger
- CSRFTokenGen func(*http.Request) string //The CSRF token generator function
+ CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function
+ Database *database.Database `json:"-"`
+ Logger *logger.Logger `json:"-"`
}
type Manager struct {
- LoadedPlugins sync.Map //Storing *Plugin
- TagPluginMap sync.Map //Storing *radix.Tree for each plugin tag
+ LoadedPlugins sync.Map //Storing *Plugin
+ tagPluginMap sync.Map //Storing *radix.Tree for each plugin tag
+ tagPluginList map[string][]*Plugin //Storing the plugin list for each tag, only concurrent READ is allowed
Options *ManagerOptions
}
diff --git a/src/mod/plugins/uirouter.go b/src/mod/plugins/ui_router.go
similarity index 100%
rename from src/mod/plugins/uirouter.go
rename to src/mod/plugins/ui_router.go
diff --git a/src/mod/plugins/zoraxy_plugin/dynamic_router.go b/src/mod/plugins/zoraxy_plugin/dynamic_router.go
new file mode 100644
index 0000000..1dc53ce
--- /dev/null
+++ b/src/mod/plugins/zoraxy_plugin/dynamic_router.go
@@ -0,0 +1,162 @@
+package zoraxy_plugin
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+)
+
+/*
+
+ Dynamic Path Handler
+
+*/
+
+type SniffResult int
+
+const (
+ SniffResultAccpet SniffResult = iota // Forward the request to this plugin dynamic capture ingress
+ SniffResultSkip // Skip this plugin and let the next plugin handle the request
+)
+
+type SniffHandler func(*DynamicSniffForwardRequest) SniffResult
+
+/*
+RegisterDynamicSniffHandler registers a dynamic sniff handler for a path
+You can decide to accept or skip the request based on the request header and paths
+*/
+func (p *PathRouter) RegisterDynamicSniffHandler(sniff_ingress string, mux *http.ServeMux, handler SniffHandler) {
+ if !strings.HasSuffix(sniff_ingress, "/") {
+ sniff_ingress = sniff_ingress + "/"
+ }
+ mux.Handle(sniff_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic sniff path: " + r.RequestURI)
+ }
+
+ // Decode the request payload
+ jsonBytes, err := io.ReadAll(r.Body)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error reading request body:", err)
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+ payload, err := DecodeForwardRequestPayload(jsonBytes)
+ if err != nil {
+ if p.enableDebugPrint {
+ fmt.Println("Error decoding request payload:", err)
+ fmt.Print("Payload: ")
+ fmt.Println(string(jsonBytes))
+ }
+ http.Error(w, "Internal Server Error", http.StatusInternalServerError)
+ return
+ }
+
+ // Get the forwarded request UUID
+ forwardUUID := r.Header.Get("X-Zoraxy-RequestID")
+ payload.requestUUID = forwardUUID
+ payload.rawRequest = r
+
+ sniffResult := handler(&payload)
+ if sniffResult == SniffResultAccpet {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("OK"))
+ } else {
+ w.WriteHeader(http.StatusNotImplemented)
+ w.Write([]byte("SKIP"))
+ }
+ }))
+}
+
+// RegisterDynamicCaptureHandle register the dynamic capture ingress path with a handler
+func (p *PathRouter) RegisterDynamicCaptureHandle(capture_ingress string, mux *http.ServeMux, handlefunc func(http.ResponseWriter, *http.Request)) {
+ if !strings.HasSuffix(capture_ingress, "/") {
+ capture_ingress = capture_ingress + "/"
+ }
+ mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if p.enableDebugPrint {
+ fmt.Println("Request captured by dynamic capture path: " + r.RequestURI)
+ }
+
+ rewrittenURL := r.RequestURI
+ rewrittenURL = strings.TrimPrefix(rewrittenURL, capture_ingress)
+ rewrittenURL = strings.ReplaceAll(rewrittenURL, "//", "/")
+ if rewrittenURL == "" {
+ rewrittenURL = "/"
+ }
+ if !strings.HasPrefix(rewrittenURL, "/") {
+ rewrittenURL = "/" + rewrittenURL
+ }
+ r.RequestURI = rewrittenURL
+
+ handlefunc(w, r)
+ }))
+}
+
+/*
+ Sniffing and forwarding
+
+ The following functions are here to help with
+ sniffing and forwarding requests to the dynamic
+ router.
+*/
+// A custom request object to be used in the dynamic sniffing
+type DynamicSniffForwardRequest struct {
+ Method string `json:"method"`
+ Hostname string `json:"hostname"`
+ URL string `json:"url"`
+ Header map[string][]string `json:"header"`
+ RemoteAddr string `json:"remote_addr"`
+ Host string `json:"host"`
+ RequestURI string `json:"request_uri"`
+ Proto string `json:"proto"`
+ ProtoMajor int `json:"proto_major"`
+ ProtoMinor int `json:"proto_minor"`
+
+ /* Internal use */
+ rawRequest *http.Request `json:"-"`
+ requestUUID string `json:"-"`
+}
+
+// GetForwardRequestPayload returns a DynamicSniffForwardRequest object from an http.Request object
+func EncodeForwardRequestPayload(r *http.Request) DynamicSniffForwardRequest {
+ return DynamicSniffForwardRequest{
+ Method: r.Method,
+ Hostname: r.Host,
+ URL: r.URL.String(),
+ Header: r.Header,
+ RemoteAddr: r.RemoteAddr,
+ Host: r.Host,
+ RequestURI: r.RequestURI,
+ Proto: r.Proto,
+ ProtoMajor: r.ProtoMajor,
+ ProtoMinor: r.ProtoMinor,
+ rawRequest: r,
+ }
+}
+
+// DecodeForwardRequestPayload decodes JSON bytes into a DynamicSniffForwardRequest object
+func DecodeForwardRequestPayload(jsonBytes []byte) (DynamicSniffForwardRequest, error) {
+ var payload DynamicSniffForwardRequest
+ err := json.Unmarshal(jsonBytes, &payload)
+ if err != nil {
+ return DynamicSniffForwardRequest{}, err
+ }
+ return payload, nil
+}
+
+// GetRequest returns the original http.Request object, for debugging purposes
+func (dsfr *DynamicSniffForwardRequest) GetRequest() *http.Request {
+ return dsfr.rawRequest
+}
+
+// GetRequestUUID returns the request UUID
+// if this UUID is empty string, that might indicate the request
+// is not coming from the dynamic router
+func (dsfr *DynamicSniffForwardRequest) GetRequestUUID() string {
+ return dsfr.requestUUID
+}
diff --git a/src/mod/plugins/zoraxy_plugin/static_router.go b/src/mod/plugins/zoraxy_plugin/static_router.go
index 13b07ff..f4abcb7 100644
--- a/src/mod/plugins/zoraxy_plugin/static_router.go
+++ b/src/mod/plugins/zoraxy_plugin/static_router.go
@@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
p.enableDebugPrint = enable
}
-func (p *PathRouter) RegisterHandle(capture_ingress string, mux *http.ServeMux) {
+// StartStaticCapture starts the static capture ingress
+func (p *PathRouter) RegisterStaticCaptureHandle(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)
+ p.staticCaptureServeHTTP(w, r)
}))
}
-func (p *PathRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+// staticCaptureServeHTTP serves the static capture path using user defined handler
+func (p *PathRouter) staticCaptureServeHTTP(w http.ResponseWriter, r *http.Request) {
capturePath := r.Header.Get("X-Zoraxy-Capture")
if capturePath != "" {
if p.enableDebugPrint {
diff --git a/src/reverseproxy.go b/src/reverseproxy.go
index 6dabdbc..ee16336 100644
--- a/src/reverseproxy.go
+++ b/src/reverseproxy.go
@@ -931,7 +931,11 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
// Report the current status of the reverse proxy server
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
- js, _ := json.Marshal(dynamicProxyRouter)
+ js, err := json.Marshal(dynamicProxyRouter)
+ if err != nil {
+ utils.SendErrorResponse(w, "Unable to marshal status data")
+ return
+ }
utils.SendJSONResponse(w, string(js))
}