mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00

- Added dynamic capture plugin example - Added dynamic capture plugin doc - Removed ztnc from plugin example
673 lines
30 KiB
HTML
673 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" class="is-white">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<link rel="icon" type="image/png" href="/favicon.png">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>
|
|
Dynamic Capture Example | Zoraxy Documentation
|
|
</title>
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
<!-- css -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
|
|
<!-- Fonts -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<!-- Code highlight -->
|
|
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css"> -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/vs2015.css">
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
|
|
<!-- additional languages -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/css.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
|
|
<style>
|
|
#msgbox{
|
|
position: fixed;
|
|
bottom: 1em;
|
|
right: 1em;
|
|
z-index: 9999;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
dialog[open] {
|
|
animation: fadeIn 0.3s ease-in-out;
|
|
}
|
|
|
|
code{
|
|
border-radius: 0.5rem;
|
|
}
|
|
</style>
|
|
<script src="/html/assets/theme.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="ts-content">
|
|
<div class="ts-container">
|
|
<div style="float: right;">
|
|
<button class="ts-button is-icon" id="darkModeToggle">
|
|
<span class="ts-icon is-moon-icon"></span>
|
|
</button>
|
|
</div>
|
|
<div class="ts-tab is-pilled">
|
|
<a href="" class="item" style="user-select: none;">
|
|
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
|
|
</a>
|
|
<a href="#!" class="is-active item">
|
|
Documents
|
|
</a>
|
|
<a href="#!" class="item">
|
|
Examples
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="ts-divider"></div>
|
|
<div>
|
|
<div class="has-padded">
|
|
<div class="ts-grid mobile:is-stacked">
|
|
<div class="column is-4-wide">
|
|
<div class="ts-box">
|
|
<div class="ts-menu is-end-icon">
|
|
<a class="item">
|
|
Introduction
|
|
<span class="ts-icon is-caret-down-icon"></span>
|
|
</a>
|
|
<div class="ts-menu is-dense is-small is-horizontally-padded">
|
|
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
|
|
What is Zoraxy Plugin
|
|
</a>
|
|
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
|
|
Getting Started
|
|
</a>
|
|
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
|
|
Installing Plugin
|
|
</a>
|
|
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
|
|
Enable Plugins
|
|
</a>
|
|
<a class="item" href="/html/1. Introduction/5. Viewing Plugin Info.html">
|
|
Viewing Plugin Info
|
|
</a>
|
|
</div>
|
|
<a class="item">
|
|
Architecture
|
|
<span class="ts-icon is-caret-down-icon"></span>
|
|
</a>
|
|
<div class="ts-menu is-dense is-small is-horizontally-padded">
|
|
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
|
|
Plugin Architecture
|
|
</a>
|
|
<a class="item" href="/html/2. Architecture/2. Introspect.html">
|
|
Introspect
|
|
</a>
|
|
<a class="item" href="/html/2. Architecture/3. Configure.html">
|
|
Configure
|
|
</a>
|
|
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
|
|
Capture Modes
|
|
</a>
|
|
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
|
|
Plugin UI
|
|
</a>
|
|
<a class="item" href="/html/2. Architecture/6. Compile a Plugin.html">
|
|
Compile a Plugin
|
|
</a>
|
|
</div>
|
|
<a class="item">
|
|
Basic Examples
|
|
<span class="ts-icon is-caret-down-icon"></span>
|
|
</a>
|
|
<div class="ts-menu is-dense is-small is-horizontally-padded">
|
|
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
|
|
Hello World
|
|
</a>
|
|
<a class="item" href="/html/3. Basic Examples/2. RESTful Example.html">
|
|
RESTful Example
|
|
</a>
|
|
<a class="item" href="/html/3. Basic Examples/3. Static Capture Example.html">
|
|
Static Capture Example
|
|
</a>
|
|
<a class="item is-active" href="/html/3. Basic Examples/4. Dynamic Capture Example.html">
|
|
Dynamic Capture Example
|
|
</a>
|
|
</div>
|
|
<a class="item" href="/html/index.html">
|
|
index
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-12-wide">
|
|
<div class="ts-box">
|
|
<div class="ts-container is-padded has-top-padded-large">
|
|
<h1 id="dynamic-capture-example">
|
|
Dynamic Capture Example
|
|
</h1>
|
|
<p>
|
|
<p class="ts-text">
|
|
Last Update: 29/05/2025
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<p>
|
|
<p class="ts-text">
|
|
This example demonstrates how to use dynamic capture in Zoraxy plugins. Dynamic capture allows you to intercept requests based on real-time conditions, so you can program your plugin in a way that it can decided if it want to handle the request or not.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
<span class="ts-text is-heavy">
|
|
Notes: This example assumes you have already read Hello World and Stataic Capture Example.
|
|
</span>
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
Lets dive in!
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="1-create-the-plugin-folder-structure">
|
|
1. Create the plugin folder structure
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
Follow the same steps as the Hello World example to set up the plugin folder structure. Refer to the Hello World example sections 1 to 5 for details.
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="2-define-introspect">
|
|
2. Define Introspect
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
The introspect configuration specifies the dynamic capture sniff and ingress paths for your plugin.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
|
|
ID: "org.aroz.zoraxy.dynamic-capture-example",
|
|
Name: "Dynamic Capture Example",
|
|
Author: "aroz.org",
|
|
AuthorContact: "https://aroz.org",
|
|
Description: "This is an example plugin for Zoraxy that demonstrates how to use dynamic captures.",
|
|
URL: "https://zoraxy.aroz.org",
|
|
Type: plugin.PluginType_Router,
|
|
VersionMajor: 1,
|
|
VersionMinor: 0,
|
|
VersionPatch: 0,
|
|
|
|
DynamicCaptureSniff: "/d_sniff",
|
|
DynamicCaptureIngress: "/d_capture",
|
|
|
|
UIPath: UI_PATH,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
Note the
|
|
<span class="ts-text is-code">
|
|
DynamicCaptureSniff
|
|
</span>
|
|
and
|
|
<span class="ts-text is-code">
|
|
DynamicCaptureIngress
|
|
</span>
|
|
. These paths define the sniffing and capturing behavior for dynamic requests. The sniff path is used to evaluate whether a request should be intercepted, while the ingress path handles the intercepted requests.
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="3-register-dynamic-capture-handlers">
|
|
3. Register Dynamic Capture Handlers
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
Dynamic capture handlers are used to process requests that match specific conditions.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">pathRouter := plugin.NewPathRouter()
|
|
pathRouter.SetDebugPrintMode(true)
|
|
|
|
pathRouter.RegisterDynamicSniffHandler("/d_sniff", http.DefaultServeMux, func(dsfr *plugin.DynamicSniffForwardRequest) plugin.SniffResult {
|
|
if strings.HasPrefix(dsfr.RequestURI, "/foobar") {
|
|
fmt.Println("Accepting request with UUID: " + dsfr.GetRequestUUID())
|
|
return plugin.SniffResultAccpet
|
|
}
|
|
fmt.Println("Skipping request with UUID: " + dsfr.GetRequestUUID())
|
|
return plugin.SniffResultSkip
|
|
})
|
|
|
|
pathRouter.RegisterDynamicCaptureHandle("/d_capture", http.DefaultServeMux, func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("Welcome to the dynamic capture handler!\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)))
|
|
}
|
|
}
|
|
})
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
The
|
|
<span class="ts-text is-code">
|
|
RegisterDynamicSniffHandler
|
|
</span>
|
|
evaluates incoming requests, while the
|
|
<span class="ts-text is-code">
|
|
RegisterDynamicCaptureHandle
|
|
</span>
|
|
processes the intercepted requests.
|
|
</p>
|
|
</p>
|
|
<h3 id="sniffing-logic">
|
|
Sniffing Logic
|
|
</h3>
|
|
<p>
|
|
If a module registered a dynamic capture path, Zoraxy will forward the request headers as
|
|
<span class="ts-text is-code">
|
|
DynamicSniffForwardRequest
|
|
</span>
|
|
(
|
|
<span class="ts-text is-code">
|
|
dsfr
|
|
</span>
|
|
) object to all the plugins that is assigned to this tag. And in each of the plugins, a dedicated logic will take in the object and “think” if they want to handle the request. You can get the following information from the dsfr object by directly accessing the members of it.
|
|
</p>
|
|
<pre><code class="language-go">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"`
|
|
}
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
You can also use the
|
|
<span class="ts-text is-code">
|
|
GetRequest()
|
|
</span>
|
|
function to get the
|
|
<span class="ts-text is-code">
|
|
*http.Request
|
|
</span>
|
|
object or
|
|
<span class="ts-text is-code">
|
|
GetRequestUUID()
|
|
</span>
|
|
to get a
|
|
<span class="ts-text is-code">
|
|
string
|
|
</span>
|
|
value that is a UUID corresponding to this request for later matching with the incoming, forwarded request.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
<span class="ts-text is-heavy">
|
|
Note that since all request will pass through the sniffing function in your plugin, do not implement any blocking logic in your sniffing function, otherwise this will slow down all traffic going through the HTTP proxy rule with the plugin enabled.
|
|
</span>
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
In the sniffing stage, you can choose to either return
|
|
<span class="ts-text is-code">
|
|
ControlStatusCode_CAPTURED
|
|
</span>
|
|
, where Zoraxy will forward the request to your plugin
|
|
<span class="ts-text is-code">
|
|
DynamicCaptureIngress
|
|
</span>
|
|
endpoint, or
|
|
<span class="ts-text is-code">
|
|
ControlStatusCode_UNHANDLED
|
|
</span>
|
|
, where Zoraxy will pass on the request to the next dynamic handling plugin or if there are no more plugins to handle the routing, to the upstream server.
|
|
</p>
|
|
</p>
|
|
<h3 id="capture-handling">
|
|
Capture Handling
|
|
</h3>
|
|
<p>
|
|
<p class="ts-text">
|
|
The capture handling is where Zoraxy formally forward you the HTTP request the client is requesting. In this situation, you must response the request by properly handling the
|
|
<span class="ts-text is-code">
|
|
http.Request
|
|
</span>
|
|
by writing to the
|
|
<span class="ts-text is-code">
|
|
http.ResponseWriter
|
|
</span>
|
|
.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
If there is a need to match the sniffing to the capture handling logic (Let say you want to design your plugin to run some kind of pre-processing before the actual request came in), you can use the
|
|
<span class="ts-text is-code">
|
|
X-Zoraxy-Requestid
|
|
</span>
|
|
header in the HTTP request. This is the same UUID as the one you get from
|
|
<span class="ts-text is-code">
|
|
dsfr.GetRequestUUID()
|
|
</span>
|
|
in the sniffing stage if they are the same request object on Zoraxy side.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
The http request that Zoraxy forwards to the plugin capture handling endpoint contains header like these.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-html">Request URI: /foobar/test
|
|
Request Method: GET
|
|
Request Headers:
|
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
|
Accept-Encoding: gzip, deflate, br, zstd
|
|
(more fileds)
|
|
X-Forwarded-For: 127.0.0.1
|
|
X-Forwarded-Proto: https
|
|
X-Real-Ip: 127.0.0.1
|
|
X-Zoraxy-Requestid: d00619b8-f39e-4c04-acd8-c3a6f55b1566
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
You can extract the
|
|
<span class="ts-text is-code">
|
|
X-Zoraxy-Requestid
|
|
</span>
|
|
value from the request header and do your matching for implementing your function if needed.
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="4-render-debug-ui">
|
|
4. Render Debug UI
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
This UI is used help validate the management Web UI is correctly shown in Zoraxy webmin interface. You should implement the required management interface for your plugin here.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">func RenderDebugUI(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprint(w, "**Plugin UI 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")
|
|
}
|
|
</code></pre>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="5-full-code">
|
|
5. Full Code
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
Here is the complete code for the dynamic capture example:
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
plugin "example.com/zoraxy/dynamic-capture-example/mod/zoraxy_plugin"
|
|
)
|
|
|
|
const (
|
|
PLUGIN_ID = "org.aroz.zoraxy.dynamic-capture-example"
|
|
UI_PATH = "/debug"
|
|
STATIC_CAPTURE_INGRESS = "/s_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: "org.aroz.zoraxy.dynamic-capture-example",
|
|
Name: "Dynamic Capture Example",
|
|
Author: "aroz.org",
|
|
AuthorContact: "https://aroz.org",
|
|
Description: "This is an example plugin for Zoraxy that demonstrates how to use dynamic captures.",
|
|
URL: "https://zoraxy.aroz.org",
|
|
Type: plugin.PluginType_Router,
|
|
VersionMajor: 1,
|
|
VersionMinor: 0,
|
|
VersionPatch: 0,
|
|
|
|
DynamicCaptureSniff: "/d_sniff",
|
|
DynamicCaptureIngress: "/d_capture",
|
|
|
|
UIPath: UI_PATH,
|
|
|
|
/*
|
|
SubscriptionPath: "/subept",
|
|
SubscriptionsEvents: []plugin.SubscriptionEvent{
|
|
*/
|
|
})
|
|
if err != nil {
|
|
//Terminate or enter standalone mode here
|
|
panic(err)
|
|
}
|
|
|
|
// Setup the path router
|
|
pathRouter := plugin.NewPathRouter()
|
|
pathRouter.SetDebugPrintMode(true)
|
|
|
|
/*
|
|
Dynamic Captures
|
|
*/
|
|
pathRouter.RegisterDynamicSniffHandler("/d_sniff", http.DefaultServeMux, func(dsfr *plugin.DynamicSniffForwardRequest) plugin.SniffResult {
|
|
//In this example, we want to capture all URI
|
|
//that start with /foobar and forward it to the dynamic capture handler
|
|
if strings.HasPrefix(dsfr.RequestURI, "/foobar") {
|
|
reqUUID := dsfr.GetRequestUUID()
|
|
fmt.Println("Accepting request with UUID: " + reqUUID)
|
|
|
|
// Print all the values of the request
|
|
fmt.Println("Method:", dsfr.Method)
|
|
fmt.Println("Hostname:", dsfr.Hostname)
|
|
fmt.Println("URL:", dsfr.URL)
|
|
fmt.Println("Header:")
|
|
for key, values := range dsfr.Header {
|
|
for _, value := range values {
|
|
fmt.Printf(" %s: %s\n", key, value)
|
|
}
|
|
}
|
|
fmt.Println("RemoteAddr:", dsfr.RemoteAddr)
|
|
fmt.Println("Host:", dsfr.Host)
|
|
fmt.Println("RequestURI:", dsfr.RequestURI)
|
|
fmt.Println("Proto:", dsfr.Proto)
|
|
fmt.Println("ProtoMajor:", dsfr.ProtoMajor)
|
|
fmt.Println("ProtoMinor:", dsfr.ProtoMinor)
|
|
|
|
// We want to handle this request, reply with aSniffResultAccept
|
|
return plugin.SniffResultAccpet
|
|
}
|
|
|
|
// If the request URI does not match, we skip this request
|
|
fmt.Println("Skipping request with UUID: " + dsfr.GetRequestUUID())
|
|
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("Dynamic capture example started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
|
|
http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
|
|
}
|
|
|
|
// Render the debug UI
|
|
func RenderDebugUI(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprint(w, "**Plugin UI 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")
|
|
}
|
|
|
|
</code></pre>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="6-expected-output">
|
|
6. Expected Output
|
|
</h2>
|
|
<p>
|
|
To enable the plugin, add the plugin to one of the tags and assign the tag to your HTTP Proxy Rule. Here is an example of assigning the plugin to the “debug” tag and assigning it to a localhost loopback HTTP proxy rule.
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
When the plugin is running, requests matching the sniff conditions will be intercepted and processed by the dynamic capture handler.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
If everything is correctly setup, you should see the following page when requesting any URL with prefix
|
|
<span class="ts-text is-code">
|
|
(your_HTTP_proxy_rule_hostname)/foobar
|
|
</span>
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<div class="ts-image is-rounded" style="max-width: 800px">
|
|
<img src="img/4. Dynamic Capture Example/image-20250530205430254.png" alt="image-20250530205430254" />
|
|
</div>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
Example terminal output for requesting
|
|
<span class="ts-text is-code">
|
|
/foobar/*
|
|
</span>
|
|
:
|
|
</p>
|
|
</p>
|
|
<pre><span class="ts-text is-code">[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Request captured by dynamic sniff path: /d_sniff/
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Header:
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Method: GET
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Hostname: a.localhost
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] URL: /foobar/test
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Accepting request with UUID: 8c916c58-0d6a-4d11-a2f0-f29d3d984509
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Fetch-Dest: document
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Accept-Encoding: gzip, deflate, br, zstd
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Accept-Language: zh-TW,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Cache-Control: max-age=0
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Fetch-User: ?1
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Upgrade-Insecure-Requests: 1
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Priority: u=0, i
|
|
[2025-05-30 20:44:26.143149] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Ch-Ua-Mobile: ?0
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Ch-Ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Ch-Ua-Platform: "Windows"
|
|
[2025-05-30 20:44:26.142645] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Fetch-Site: none
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Sec-Fetch-Mode: navigate
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] RemoteAddr: [::1]:54522
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Host: a.localhost
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] RequestURI: /foobar/test
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] Proto: HTTP/2.0
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] ProtoMajor: 2
|
|
[2025-05-30 20:44:26.143165] [plugin-manager] [system:info] [Dynamic Capture Example:22964] ProtoMinor: 0
|
|
</span></pre>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<p>
|
|
<p class="ts-text">
|
|
Now you know how to develop a plugin in Zoraxy that handles special routings!
|
|
</p>
|
|
</p>
|
|
</div>
|
|
<br>
|
|
<br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="ts-container">
|
|
<div class="ts-divider"></div>
|
|
<div class="ts-content">
|
|
<div class="ts-text">
|
|
Zoraxy © tobychui
|
|
<span class="thisyear">
|
|
2025
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
$(".thisyear").text(new Date().getFullYear());
|
|
</script>
|
|
<script>
|
|
hljs.highlightAll();
|
|
</script>
|
|
</body>
|
|
</html> |