mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-04 22:57:20 +02:00
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:
parent
39d6d16c2a
commit
23d4df1ed7
@ -3,7 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
plugin "aroz.org/zoraxy/debugger/mod/zoraxy_plugin"
|
plugin "aroz.org/zoraxy/debugger/mod/zoraxy_plugin"
|
||||||
)
|
)
|
||||||
@ -39,6 +41,9 @@ func main() {
|
|||||||
},
|
},
|
||||||
StaticCaptureIngress: "/s_capture",
|
StaticCaptureIngress: "/s_capture",
|
||||||
|
|
||||||
|
DynamicCaptureSniff: "/d_sniff",
|
||||||
|
DynamicCaptureIngress: "/d_capture",
|
||||||
|
|
||||||
UIPath: UI_PATH,
|
UIPath: UI_PATH,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -51,9 +56,13 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create a path handler for the capture paths
|
// Setup the path router
|
||||||
pathRouter := plugin.NewPathRouter()
|
pathRouter := plugin.NewPathRouter()
|
||||||
pathRouter.SetDebugPrintMode(true)
|
pathRouter.SetDebugPrintMode(true)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Static Routers
|
||||||
|
*/
|
||||||
pathRouter.RegisterPathHandler("/test_a", http.HandlerFunc(HandleCaptureA))
|
pathRouter.RegisterPathHandler("/test_a", http.HandlerFunc(HandleCaptureA))
|
||||||
pathRouter.RegisterPathHandler("/test_b", http.HandlerFunc(HandleCaptureB))
|
pathRouter.RegisterPathHandler("/test_b", http.HandlerFunc(HandleCaptureB))
|
||||||
pathRouter.SetDefaultHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte("This request is captured by the default handler!<br>Request URI: " + r.URL.String()))
|
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)
|
http.HandleFunc(UI_PATH+"/", RenderDebugUI)
|
||||||
fmt.Println("Debugger started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
|
fmt.Println("Debugger started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
|
||||||
@ -72,21 +120,21 @@ func main() {
|
|||||||
|
|
||||||
// Handle the captured request
|
// Handle the captured request
|
||||||
func HandleCaptureA(w http.ResponseWriter, r *http.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 {
|
for _, value := range values {
|
||||||
fmt.Printf("%s: %s\n", key, value)
|
fmt.Printf("%s: %s\n", key, value)
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte("This request is captured by A handler!<br>Request URI: " + r.URL.String()))
|
w.Write([]byte("This request is captured by A handler!<br>Request URI: " + r.URL.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleCaptureB(w http.ResponseWriter, r *http.Request) {
|
func HandleCaptureB(w http.ResponseWriter, r *http.Request) {
|
||||||
for key, values := range r.Header {
|
/*for key, values := range r.Header {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
fmt.Printf("%s: %s\n", key, value)
|
fmt.Printf("%s: %s\n", key, value)
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
w.Write([]byte("This request is captured by the B handler!<br>Request URI: " + r.URL.String()))
|
w.Write([]byte("This request is captured by the B handler!<br>Request URI: " + r.URL.String()))
|
||||||
}
|
}
|
||||||
|
162
example/plugins/debugger/mod/zoraxy_plugin/dynamic_router.go
Normal file
162
example/plugins/debugger/mod/zoraxy_plugin/dynamic_router.go
Normal 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
|
||||||
|
}
|
@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
|
|||||||
p.enableDebugPrint = enable
|
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, "/") {
|
if !strings.HasSuffix(capture_ingress, "/") {
|
||||||
capture_ingress = capture_ingress + "/"
|
capture_ingress = capture_ingress + "/"
|
||||||
}
|
}
|
||||||
mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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")
|
capturePath := r.Header.Get("X-Zoraxy-Capture")
|
||||||
if capturePath != "" {
|
if capturePath != "" {
|
||||||
if p.enableDebugPrint {
|
if p.enableDebugPrint {
|
||||||
|
162
example/plugins/helloworld/mod/zoraxy_plugin/dynamic_router.go
Normal file
162
example/plugins/helloworld/mod/zoraxy_plugin/dynamic_router.go
Normal 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
|
||||||
|
}
|
@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
|
|||||||
p.enableDebugPrint = enable
|
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, "/") {
|
if !strings.HasSuffix(capture_ingress, "/") {
|
||||||
capture_ingress = capture_ingress + "/"
|
capture_ingress = capture_ingress + "/"
|
||||||
}
|
}
|
||||||
mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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")
|
capturePath := r.Header.Get("X-Zoraxy-Capture")
|
||||||
if capturePath != "" {
|
if capturePath != "" {
|
||||||
if p.enableDebugPrint {
|
if p.enableDebugPrint {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
module aroz.org/zoraxy/example/static_capture
|
|
||||||
|
|
||||||
go 1.23.6
|
|
@ -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!<br>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!<br>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!<br>Request URI: " + r.URL.String()))
|
|
||||||
}
|
|
@ -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
|
|
@ -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)
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
}
|
|
@ -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)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
@ -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")
|
|
||||||
}
|
|
162
example/plugins/ztnc/mod/zoraxy_plugin/dynamic_router.go
Normal file
162
example/plugins/ztnc/mod/zoraxy_plugin/dynamic_router.go
Normal 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
|
||||||
|
}
|
@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
|
|||||||
p.enableDebugPrint = enable
|
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, "/") {
|
if !strings.HasSuffix(capture_ingress, "/") {
|
||||||
capture_ingress = capture_ingress + "/"
|
capture_ingress = capture_ingress + "/"
|
||||||
}
|
}
|
||||||
mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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")
|
capturePath := r.Header.Get("X-Zoraxy-Capture")
|
||||||
if capturePath != "" {
|
if capturePath != "" {
|
||||||
if p.enableDebugPrint {
|
if p.enableDebugPrint {
|
||||||
|
111
src/mod/plugins/dynamic_forwarder.go
Normal file
111
src/mod/plugins/dynamic_forwarder.go
Normal file
@ -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
|
||||||
|
}
|
@ -46,6 +46,7 @@ func (m *Manager) StartPlugin(pluginID string) error {
|
|||||||
}
|
}
|
||||||
js, _ := json.Marshal(pluginConfiguration)
|
js, _ := json.Marshal(pluginConfiguration)
|
||||||
|
|
||||||
|
//Start the plugin with given configuration
|
||||||
m.Log("Starting plugin "+thisPlugin.Spec.Name+" at :"+strconv.Itoa(pluginConfiguration.Port), nil)
|
m.Log("Starting plugin "+thisPlugin.Spec.Name+" at :"+strconv.Itoa(pluginConfiguration.Port), nil)
|
||||||
cmd := exec.Command(absolutePath, "-configure="+string(js))
|
cmd := exec.Command(absolutePath, "-configure="+string(js))
|
||||||
cmd.Dir = filepath.Dir(absolutePath)
|
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
|
//Create a new static forwarder router for each of the static capture paths
|
||||||
plugin.(*Plugin).StartAllStaticPathRouters()
|
plugin.(*Plugin).StartAllStaticPathRouters()
|
||||||
|
|
||||||
|
//If the plugin contains dynamic capture, create a dynamic capture handler
|
||||||
|
if thisPlugin.AcceptDynamicRoute() {
|
||||||
|
plugin.(*Plugin).StartDynamicForwardRouter()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +209,7 @@ func (m *Manager) StopPlugin(pluginID string) error {
|
|||||||
thisPlugin.uiProxy = nil
|
thisPlugin.uiProxy = nil
|
||||||
plugin.(*Plugin).Enabled = false
|
plugin.(*Plugin).Enabled = false
|
||||||
plugin.(*Plugin).StopAllStaticPathRouters()
|
plugin.(*Plugin).StopAllStaticPathRouters()
|
||||||
|
plugin.(*Plugin).StopDynamicForwardRouter()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ func NewPluginManager(options *ManagerOptions) *Manager {
|
|||||||
|
|
||||||
return &Manager{
|
return &Manager{
|
||||||
LoadedPlugins: sync.Map{},
|
LoadedPlugins: sync.Map{},
|
||||||
TagPluginMap: sync.Map{},
|
tagPluginMap: sync.Map{},
|
||||||
|
tagPluginList: make(map[string][]*Plugin),
|
||||||
Options: options,
|
Options: options,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,7 +77,7 @@ func (m *Manager) LoadPluginsFromDisk() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Generate the static forwarder radix tree
|
//Generate the static forwarder radix tree
|
||||||
m.UpdateTagsToTree()
|
m.UpdateTagsToPluginMaps()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -98,7 +99,7 @@ func (m *Manager) EnablePlugin(pluginID string) error {
|
|||||||
}
|
}
|
||||||
m.Options.Database.Write("plugins", pluginID, true)
|
m.Options.Database.Write("plugins", pluginID, true)
|
||||||
//Generate the static forwarder radix tree
|
//Generate the static forwarder radix tree
|
||||||
m.UpdateTagsToTree()
|
m.UpdateTagsToPluginMaps()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +111,7 @@ func (m *Manager) DisablePlugin(pluginID string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//Generate the static forwarder radix tree
|
//Generate the static forwarder radix tree
|
||||||
m.UpdateTagsToTree()
|
m.UpdateTagsToPluginMaps()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,11 +185,17 @@ func (m *Plugin) StartAllStaticPathRouters() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopAllStaticPathRouters stops all static path routers
|
||||||
func (m *Plugin) StopAllStaticPathRouters() {
|
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, "/")
|
longestPrefix = strings.TrimSuffix(longestPrefix, "/")
|
||||||
targetRouter := p.staticRouteProxy[longestPrefix]
|
targetRouter := p.staticRouteProxy[longestPrefix]
|
||||||
if targetRouter == nil {
|
if targetRouter == nil {
|
||||||
|
@ -15,12 +15,25 @@ import (
|
|||||||
request path registered when the plugin started
|
request path registered when the plugin started
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (m *Manager) UpdateTagsToTree() {
|
func (m *Manager) UpdateTagsToPluginMaps() {
|
||||||
//build the tag to plugin pointer sync.Map
|
//build the tag to plugin pointer sync.Map
|
||||||
m.TagPluginMap = sync.Map{}
|
m.tagPluginMap = sync.Map{}
|
||||||
for tag, pluginIds := range m.Options.PluginGroups {
|
for tag, pluginIds := range m.Options.PluginGroups {
|
||||||
tree := m.GetForwarderRadixTreeFromPlugins(pluginIds)
|
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 {
|
} else {
|
||||||
//The path has already been assigned to another plugin
|
//The path has already been assigned to another plugin
|
||||||
pluginList, _ := r.Get(captureRule.CapturePath)
|
pluginList, _ := r.Get(captureRule.CapturePath)
|
||||||
//pluginList = append(pluginList.([]*Plugin), plugin)
|
|
||||||
//r.Insert(captureRule.CapturePath, pluginList)
|
|
||||||
|
|
||||||
//Warn the path is already assigned to another plugin
|
//Warn the path is already assigned to another plugin
|
||||||
if plugin.Spec.ID == pluginList.([]*Plugin)[0].Spec.ID {
|
if plugin.Spec.ID == pluginList.([]*Plugin)[0].Spec.ID {
|
||||||
|
@ -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
|
|
||||||
}
|
|
73
src/mod/plugins/traffic_router.go
Normal file
73
src/mod/plugins/traffic_router.go
Normal file
@ -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
|
||||||
|
}
|
@ -24,20 +24,24 @@ type Plugin struct {
|
|||||||
AssignedPort int //The assigned port for the plugin
|
AssignedPort int //The assigned port for the plugin
|
||||||
uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI
|
uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI
|
||||||
staticRouteProxy map[string]*dpcore.ReverseProxy //Storing longest prefix => dpcore map for static route
|
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
|
process *exec.Cmd //The process of the plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManagerOptions struct {
|
type ManagerOptions struct {
|
||||||
PluginDir string //The directory where the plugins are stored
|
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
|
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
|
SystemConst *zoraxyPlugin.RuntimeConstantValue
|
||||||
Database *database.Database
|
CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function
|
||||||
Logger *logger.Logger
|
Database *database.Database `json:"-"`
|
||||||
CSRFTokenGen func(*http.Request) string //The CSRF token generator function
|
Logger *logger.Logger `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
LoadedPlugins sync.Map //Storing *Plugin
|
LoadedPlugins sync.Map //Storing *Plugin
|
||||||
TagPluginMap sync.Map //Storing *radix.Tree for each plugin tag
|
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
|
Options *ManagerOptions
|
||||||
}
|
}
|
||||||
|
162
src/mod/plugins/zoraxy_plugin/dynamic_router.go
Normal file
162
src/mod/plugins/zoraxy_plugin/dynamic_router.go
Normal 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
|
||||||
|
}
|
@ -43,16 +43,18 @@ func (p *PathRouter) SetDebugPrintMode(enable bool) {
|
|||||||
p.enableDebugPrint = enable
|
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, "/") {
|
if !strings.HasSuffix(capture_ingress, "/") {
|
||||||
capture_ingress = capture_ingress + "/"
|
capture_ingress = capture_ingress + "/"
|
||||||
}
|
}
|
||||||
mux.Handle(capture_ingress, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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")
|
capturePath := r.Header.Get("X-Zoraxy-Capture")
|
||||||
if capturePath != "" {
|
if capturePath != "" {
|
||||||
if p.enableDebugPrint {
|
if p.enableDebugPrint {
|
||||||
|
@ -931,7 +931,11 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
// Report the current status of the reverse proxy server
|
// Report the current status of the reverse proxy server
|
||||||
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
|
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))
|
utils.SendJSONResponse(w, string(js))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user