Added dynamic capture capabilities to plugin API

- Added dynamic capture ingress and sniff endpoint
- Removed static capture example (API update)
This commit is contained in:
Toby Chui
2025-03-02 12:26:44 +08:00
parent 39d6d16c2a
commit 23d4df1ed7
25 changed files with 961 additions and 644 deletions

View File

@ -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!<br>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!<br>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!<br>Request URI: " + r.URL.String()))
}

View File

@ -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
}

View File

@ -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 {