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

- Added restful-api example plugin - Added more plugin docs - Added zoraxy_plugin HandleFunc API
787 lines
35 KiB
HTML
787 lines
35 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>
|
|
RESTful 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">
|
|
<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/xml.min.js"></script>
|
|
<script>
|
|
hljs.highlightAll();
|
|
</script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
|
|
<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>
|
|
</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>
|
|
</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 is-active" href="/html/3. Basic Examples/2. RESTful Example.html">
|
|
RESTful 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="restful-api-call-in-web-ui">
|
|
RESTful API Call in Web UI
|
|
</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">
|
|
When developing a UI for your plugin, sometime you might need to make RESTFUL API calls to your plugin backend for setting up something or getting latest information from your plugin. In this example, I will show you how to create a plugin with RESTful api call capabilities with the embedded web server and the custom
|
|
<span class="ts-text is-code">
|
|
cjax
|
|
</span>
|
|
function.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
<span class="ts-text is-heavy">
|
|
Notes: This example assumes you have basic understanding on how to use jQuery
|
|
<span class="ts-text is-code">
|
|
ajax
|
|
</span>
|
|
request.
|
|
</span>
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
Lets get started!
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="1-create-the-plugin-folder-structures">
|
|
1. Create the plugin folder structures
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
This step is identical to the Hello World example, where you create a plugin folder with the required go project structure in the folder. Please refer to the Hello World example section 1 to 5 for details.
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="2-create-introspect">
|
|
2. Create Introspect
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
This is quite similar to the Hello World example as well, but we are changing some of the IDs to match what we want to do in this plugin.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
|
|
ID: "com.example.restful-example",
|
|
Name: "Restful Example",
|
|
Author: "foobar",
|
|
AuthorContact: "admin@example.com",
|
|
Description: "A simple demo for making RESTful API calls in plugin",
|
|
URL: "https://example.com",
|
|
Type: plugin.PluginType_Utilities,
|
|
VersionMajor: 1,
|
|
VersionMinor: 0,
|
|
VersionPatch: 0,
|
|
|
|
// As this is a utility plugin, we don't need to capture any traffic
|
|
// but only serve the UI, so we set the UI (relative to the plugin path) to "/"
|
|
UIPath: UI_PATH,
|
|
})
|
|
if err != nil {
|
|
//Terminate or enter standalone mode here
|
|
panic(err)
|
|
}
|
|
</code></pre>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="3-create-an-embedded-web-server-with-handlers">
|
|
3. Create an embedded web server with handlers
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
In this step, we create a basic embedded web file handler similar to the Hello World example, however, we will need to add a
|
|
<span class="ts-text is-code">
|
|
http.HandleFunc
|
|
</span>
|
|
to the plugin so our front-end can request and communicate with the backend.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH)
|
|
embedWebRouter.RegisterTerminateHandler(func() {
|
|
fmt.Println("Restful-example Exited")
|
|
}, nil)
|
|
|
|
//Register a simple API endpoint that will echo the request body
|
|
// Since we are using the default http.ServeMux, we can register the handler directly with the last
|
|
// parameter as nil
|
|
embedWebRouter.HandleFunc("/api/echo", func(w http.ResponseWriter, r *http.Request) {
|
|
//Some handler code here
|
|
}, nil)
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
The
|
|
<span class="ts-text is-code">
|
|
embedWebRouter.HandleFunc
|
|
</span>
|
|
last parameter is the
|
|
<span class="ts-text is-code">
|
|
http.Mux
|
|
</span>
|
|
, where if you have multiple web server listening interface, you can fill in different Mux based on your implementation. On of the examples is that, when you are developing a static web server plugin, where you need a dedicated HTTP listening endpoint that is not the one Zoraxy assigned to your plugin, you need to create two http.Mux and assign one of them for Zoraxy plugin UI purpose.
|
|
</p>
|
|
</p>
|
|
<div class="ts-divider has-top-spaced-large"></div>
|
|
<h2 id="4-modify-the-front-end-html-file-to-make-request-to-backend">
|
|
4. Modify the front-end HTML file to make request to backend
|
|
</h2>
|
|
<p>
|
|
<p class="ts-text">
|
|
To make a RESTFUL API to your plugin,
|
|
<span class="ts-text is-heavy">
|
|
you must use relative path in your request URL
|
|
</span>
|
|
.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
Absolute path that start with
|
|
<span class="ts-text is-code">
|
|
/
|
|
</span>
|
|
is only use for accessing Zoraxy resouces. For example, when you access
|
|
<span class="ts-text is-code">
|
|
/img/logo.svg
|
|
</span>
|
|
, Zoraxy webmin HTTP router will return the logo of Zoraxy for you instead of
|
|
<span class="ts-text is-code">
|
|
/plugins/your_plugin_name/{your_web_root}/img/logo.svg
|
|
</span>
|
|
.
|
|
</p>
|
|
</p>
|
|
<h3 id="making-get-request">
|
|
Making GET request
|
|
</h3>
|
|
<p>
|
|
Making GET request is similar to what you would do in ordinary web development, but only limited to relative paths like
|
|
<span class="ts-text is-code">
|
|
./api/foo/bar
|
|
</span>
|
|
instead. Here is an example on a front-end and back-end implementation of a simple “echo” API.
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
The API logic is simple: when you make a GET request to the API with
|
|
<span class="ts-text is-code">
|
|
?name=foobar
|
|
</span>
|
|
, it returns
|
|
<span class="ts-text is-code">
|
|
Hello foobar
|
|
</span>
|
|
. Here is the backend implementation in your plugin code.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">embedWebRouter.HandleFunc("/api/echo", func(w http.ResponseWriter, r *http.Request) {
|
|
// This is a simple echo API that will return the request body as response
|
|
name := r.URL.Query().Get("name")
|
|
if name == "" {
|
|
http.Error(w, "Missing 'name' query parameter", http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
response := map[string]string{"message": fmt.Sprintf("Hello %s", name)}
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
|
}
|
|
}, nil)
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
And here is the front-end code in your HTML file
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-html"><!-- The example below show how HTTP GET is used with Zoraxy plugin -->
|
|
<h3>Echo Test (HTTP GET)</h3>
|
|
<div class="ui form">
|
|
<div class="field">
|
|
<label for="nameInput">Enter your name:</label>
|
|
<input type="text" id="nameInput" placeholder="Your name">
|
|
</div>
|
|
<button class="ui button primary" id="sendRequestButton">Send Request</button>
|
|
<div class="ui message" id="responseMessage" style="display: none;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('sendRequestButton').addEventListener('click', function() {
|
|
const name = document.getElementById('nameInput').value;
|
|
if (name.trim() === "") {
|
|
alert("Please enter a name.");
|
|
return;
|
|
}
|
|
// Note the relative path is used here!
|
|
// GET do not require CSRF token, so you can use $.ajax directly
|
|
// or $.cjax (defined in /script/utils.js) to make GET request
|
|
$.ajax({
|
|
url: `./api/echo`,
|
|
type: 'GET',
|
|
data: { name: name },
|
|
success: function(data) {
|
|
console.log('Response:', data.message);
|
|
$('#responseMessage').text(data.message).show();
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Error:', error);
|
|
$('#responseMessage').text('An error occurred while processing your request.').show();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</code></pre>
|
|
<h3 id="making-post-request">
|
|
Making POST request
|
|
</h3>
|
|
<p>
|
|
<p class="ts-text">
|
|
Making POST request is also similar to GET request, except when making the request, you will need pass the CSRF-Token with the payload. This is required due to security reasons (See
|
|
<a href="https://github.com/tobychui/zoraxy/issues/267" target="_blank">
|
|
#267
|
|
</a>
|
|
for more details).
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
Since the CSRF validation is done by Zoraxy, your plugin backend code can be implemented just like an ordinary handler. Here is an example POST handling function that receive a FORM POST and print it in an HTML response.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">embedWebRouter.HandleFunc("/api/post", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "Failed to parse form data", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
for key, values := range r.PostForm {
|
|
for _, value := range values {
|
|
// Generate a simple HTML response
|
|
w.Header().Set("Content-Type", "text/html")
|
|
fmt.Fprintf(w, "%s: %s<br>", key, value)
|
|
}
|
|
}
|
|
}, nil)
|
|
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
For the front-end, you will need to use the
|
|
<span class="ts-text is-code">
|
|
$.cjax
|
|
</span>
|
|
function implemented in Zoraxy
|
|
<span class="ts-text is-code">
|
|
utils.js
|
|
</span>
|
|
file. You can include this file by adding these two lines to your HTML file.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-html"><script src="/script/jquery-3.6.0.min.js"></script>
|
|
<script src="/script/utils.js"></script>
|
|
<!- More lines here -->
|
|
<!-- The example below shows how form post can be used in plugin -->
|
|
<h3>Form Post Test (HTTP POST)</h3>
|
|
<div class="ui form">
|
|
<div class="field">
|
|
<label for="postNameInput">Name:</label>
|
|
<input type="text" id="postNameInput" placeholder="Your name">
|
|
</div>
|
|
<div class="field">
|
|
<label for="postAgeInput">Age:</label>
|
|
<input type="number" id="postAgeInput" placeholder="Your age">
|
|
</div>
|
|
<div class="field">
|
|
<label>Gender:</label>
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="genderMale" name="gender" value="Male">
|
|
<label for="genderMale">Male</label>
|
|
</div>
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="genderFemale" name="gender" value="Female">
|
|
<label for="genderFemale">Female</label>
|
|
</div>
|
|
</div>
|
|
<button class="ui button primary" id="postRequestButton">Send</button>
|
|
<div class="ui message" id="postResponseMessage" style="display: none;"></div>
|
|
</div>
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
After that, you can call to the
|
|
<span class="ts-text is-code">
|
|
$.cjax
|
|
</span>
|
|
function just like what you would usually do with the
|
|
<span class="ts-text is-code">
|
|
$ajax
|
|
</span>
|
|
function.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-javascript">// .cjax (defined in /script/utils.js) is used to make POST request with CSRF token support
|
|
// alternatively you can use $.ajax with CSRF token in headers
|
|
// the header is named "X-CSRF-Token" and the value is taken from the head
|
|
// meta tag content (i.e. <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">)
|
|
$.cjax({
|
|
url: './api/post',
|
|
type: 'POST',
|
|
data: { name: name, age: age, gender: gender },
|
|
success: function(data) {
|
|
console.log('Response:', data);
|
|
$('#postResponseMessage').html(data).show();
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Error:', error);
|
|
$('#postResponseMessage').text('An error occurred while processing your request.').show();
|
|
}
|
|
});
|
|
</code></pre>
|
|
<h3 id="post-request-with-vanilla-js">
|
|
POST Request with Vanilla JS
|
|
</h3>
|
|
<p>
|
|
<p class="ts-text">
|
|
It is possible to make POST request with Vanilla JS. Note that you will need to populate the csrf-token field yourself to make the request pass through the plugin UI request router in Zoraxy. Here is a basic example on how it could be done.
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-javascript">fetch('./api/post', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrfToken // Include the CSRF token in the headers
|
|
},
|
|
body: JSON.stringify({{your_data_here}})
|
|
})
|
|
</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 full code of the RESTFUL example for reference.
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<p class="ts-text">
|
|
Front-end (
|
|
<span class="ts-text is-code">
|
|
plugins/restful-example/www/index.html
|
|
</span>
|
|
)
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-html"><!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<!-- CSRF token, if your plugin need to make POST request to backend -->
|
|
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
|
|
<link rel="stylesheet" href="/script/semantic/semantic.min.css">
|
|
<script src="/script/jquery-3.6.0.min.js"></script>
|
|
<script src="/script/semantic/semantic.min.js"></script>
|
|
<script src="/script/utils.js"></script>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="stylesheet" href="/main.css">
|
|
<title>RESTful Example</title>
|
|
<style>
|
|
body {
|
|
background-color: var(--theme_bg_primary);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Dark theme script must be included after body tag-->
|
|
<link rel="stylesheet" href="/darktheme.css">
|
|
<script src="/script/darktheme.js"></script>
|
|
<br>
|
|
<div class="standardContainer">
|
|
<div class="ui container">
|
|
<h1>RESTFul API Example</h1>
|
|
<!-- The example below show how HTTP GET is used with Zoraxy plugin -->
|
|
<h3>Echo Test (HTTP GET)</h3>
|
|
<div class="ui form">
|
|
<div class="field">
|
|
<label for="nameInput">Enter your name:</label>
|
|
<input type="text" id="nameInput" placeholder="Your name">
|
|
</div>
|
|
<button class="ui button primary" id="sendRequestButton">Send Request</button>
|
|
<div class="ui message" id="responseMessage" style="display: none;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('sendRequestButton').addEventListener('click', function() {
|
|
const name = document.getElementById('nameInput').value;
|
|
if (name.trim() === "") {
|
|
alert("Please enter a name.");
|
|
return;
|
|
}
|
|
// Note the relative path is used here!
|
|
// GET do not require CSRF token, so you can use $.ajax directly
|
|
// or $.cjax (defined in /script/utils.js) to make GET request
|
|
$.ajax({
|
|
url: `./api/echo`,
|
|
type: 'GET',
|
|
data: { name: name },
|
|
success: function(data) {
|
|
console.log('Response:', data.message);
|
|
$('#responseMessage').text(data.message).show();
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Error:', error);
|
|
$('#responseMessage').text('An error occurred while processing your request.').show();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
<!-- The example below shows how form post can be used in plugin -->
|
|
<h3>Form Post Test (HTTP POST)</h3>
|
|
<div class="ui form">
|
|
<div class="field">
|
|
<label for="postNameInput">Name:</label>
|
|
<input type="text" id="postNameInput" placeholder="Your name">
|
|
</div>
|
|
<div class="field">
|
|
<label for="postAgeInput">Age:</label>
|
|
<input type="number" id="postAgeInput" placeholder="Your age">
|
|
</div>
|
|
<div class="field">
|
|
<label>Gender:</label>
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="genderMale" name="gender" value="Male">
|
|
<label for="genderMale">Male</label>
|
|
</div>
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="genderFemale" name="gender" value="Female">
|
|
<label for="genderFemale">Female</label>
|
|
</div>
|
|
</div>
|
|
<button class="ui button primary" id="postRequestButton">Send</button>
|
|
<div class="ui message" id="postResponseMessage" style="display: none;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('postRequestButton').addEventListener('click', function() {
|
|
const name = document.getElementById('postNameInput').value;
|
|
const age = document.getElementById('postAgeInput').value;
|
|
const genderMale = document.getElementById('genderMale').checked;
|
|
const genderFemale = document.getElementById('genderFemale').checked;
|
|
|
|
if (name.trim() === "" || age.trim() === "" || (!genderMale && !genderFemale)) {
|
|
alert("Please fill out all fields.");
|
|
return;
|
|
}
|
|
|
|
const gender = genderMale ? "Male" : "Female";
|
|
|
|
// .cjax (defined in /script/utils.js) is used to make POST request with CSRF token support
|
|
// alternatively you can use $.ajax with CSRF token in headers
|
|
// the header is named "X-CSRF-Token" and the value is taken from the head
|
|
// meta tag content (i.e. <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">)
|
|
$.cjax({
|
|
url: './api/post',
|
|
type: 'POST',
|
|
data: { name: name, age: age, gender: gender },
|
|
success: function(data) {
|
|
console.log('Response:', data);
|
|
$('#postResponseMessage').html(data).show();
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error('Error:', error);
|
|
$('#postResponseMessage').text('An error occurred while processing your request.').show();
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
Backend (
|
|
<span class="ts-text is-code">
|
|
plugins/restful-example/main.go
|
|
</span>
|
|
)
|
|
</p>
|
|
</p>
|
|
<pre><code class="language-go">package main
|
|
|
|
import (
|
|
"embed"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
plugin "example.com/zoraxy/restful-example/mod/zoraxy_plugin"
|
|
)
|
|
|
|
const (
|
|
PLUGIN_ID = "com.example.restful-example"
|
|
UI_PATH = "/"
|
|
WEB_ROOT = "/www"
|
|
)
|
|
|
|
//go:embed www/*
|
|
var content embed.FS
|
|
|
|
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: "com.example.restful-example",
|
|
Name: "Restful Example",
|
|
Author: "foobar",
|
|
AuthorContact: "admin@example.com",
|
|
Description: "A simple demo for making RESTful API calls in plugin",
|
|
URL: "https://example.com",
|
|
Type: plugin.PluginType_Utilities,
|
|
VersionMajor: 1,
|
|
VersionMinor: 0,
|
|
VersionPatch: 0,
|
|
|
|
// As this is a utility plugin, we don't need to capture any traffic
|
|
// but only serve the UI, so we set the UI (relative to the plugin path) to "/"
|
|
UIPath: UI_PATH,
|
|
})
|
|
if err != nil {
|
|
//Terminate or enter standalone mode here
|
|
panic(err)
|
|
}
|
|
|
|
// Create a new PluginEmbedUIRouter that will serve the UI from web folder
|
|
// The router will also help to handle the termination of the plugin when
|
|
// a user wants to stop the plugin via Zoraxy Web UI
|
|
embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH)
|
|
embedWebRouter.RegisterTerminateHandler(func() {
|
|
// Do cleanup here if needed
|
|
fmt.Println("Restful-example Exited")
|
|
}, nil)
|
|
|
|
//Register a simple API endpoint that will echo the request body
|
|
// Since we are using the default http.ServeMux, we can register the handler directly with the last
|
|
// parameter as nil
|
|
embedWebRouter.HandleFunc("/api/echo", func(w http.ResponseWriter, r *http.Request) {
|
|
// This is a simple echo API that will return the request body as response
|
|
name := r.URL.Query().Get("name")
|
|
if name == "" {
|
|
http.Error(w, "Missing 'name' query parameter", http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
response := map[string]string{"message": fmt.Sprintf("Hello %s", name)}
|
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
|
}
|
|
}, nil)
|
|
|
|
// Here is another example of a POST API endpoint that will echo the form data
|
|
// This will handle POST requests to /api/post and return the form data as response
|
|
embedWebRouter.HandleFunc("/api/post", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
http.Error(w, "Failed to parse form data", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
for key, values := range r.PostForm {
|
|
for _, value := range values {
|
|
// Generate a simple HTML response
|
|
w.Header().Set("Content-Type", "text/html")
|
|
fmt.Fprintf(w, "%s: %s<br>", key, value)
|
|
}
|
|
}
|
|
}, nil)
|
|
|
|
// Serve the restful-example page in the www folder
|
|
http.Handle(UI_PATH, embedWebRouter.Handler())
|
|
fmt.Println("Restful-example started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
|
|
err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
}
|
|
|
|
</code></pre>
|
|
<p>
|
|
<p class="ts-text">
|
|
What you should expect to see if everything is correctly loaded and working in Zoray
|
|
</p>
|
|
</p>
|
|
<p>
|
|
<div class="ts-image is-rounded" style="max-width: 800px">
|
|
<img src="img/2. RESTful Example/image-20250530153148506.png" alt="image-20250530153148506" />
|
|
</div>
|
|
</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>
|
|
</body>
|
|
</html> |