zoraxy/docs/plugins/html/3. Basic Examples/2. RESTful Example.html
Toby Chui ddb1a8773e Added restful-api plugin
- Added restful-api example plugin
- Added more plugin docs
- Added zoraxy_plugin HandleFunc API
2025-05-30 15:57:59 +08:00

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(&amp;plugin.IntroSpect{
ID: &quot;com.example.restful-example&quot;,
Name: &quot;Restful Example&quot;,
Author: &quot;foobar&quot;,
AuthorContact: &quot;admin@example.com&quot;,
Description: &quot;A simple demo for making RESTful API calls in plugin&quot;,
URL: &quot;https://example.com&quot;,
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 &quot;/&quot;
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, &amp;content, WEB_ROOT, UI_PATH)
embedWebRouter.RegisterTerminateHandler(func() {
fmt.Println(&quot;Restful-example Exited&quot;)
}, 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(&quot;/api/echo&quot;, 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 &ldquo;echo&rdquo; 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(&quot;/api/echo&quot;, 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(&quot;name&quot;)
if name == &quot;&quot; {
http.Error(w, &quot;Missing 'name' query parameter&quot;, http.StatusBadRequest)
return
}
w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
response := map[string]string{&quot;message&quot;: fmt.Sprintf(&quot;Hello %s&quot;, name)}
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, &quot;Failed to encode response&quot;, 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">&lt;!-- The example below show how HTTP GET is used with Zoraxy plugin --&gt;
&lt;h3&gt;Echo Test (HTTP GET)&lt;/h3&gt;
&lt;div class=&quot;ui form&quot;&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label for=&quot;nameInput&quot;&gt;Enter your name:&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;nameInput&quot; placeholder=&quot;Your name&quot;&gt;
&lt;/div&gt;
&lt;button class=&quot;ui button primary&quot; id=&quot;sendRequestButton&quot;&gt;Send Request&lt;/button&gt;
&lt;div class=&quot;ui message&quot; id=&quot;responseMessage&quot; style=&quot;display: none;&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
document.getElementById('sendRequestButton').addEventListener('click', function() {
const name = document.getElementById('nameInput').value;
if (name.trim() === &quot;&quot;) {
alert(&quot;Please enter a name.&quot;);
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();
}
});
});
&lt;/script&gt;
</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(&quot;/api/post&quot;, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, &quot;Invalid request method&quot;, http.StatusMethodNotAllowed)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, &quot;Failed to parse form data&quot;, http.StatusBadRequest)
return
}
for key, values := range r.PostForm {
for _, value := range values {
// Generate a simple HTML response
w.Header().Set(&quot;Content-Type&quot;, &quot;text/html&quot;)
fmt.Fprintf(w, &quot;%s: %s&lt;br&gt;&quot;, 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">&lt;script src=&quot;/script/jquery-3.6.0.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;/script/utils.js&quot;&gt;&lt;/script&gt;
&lt;!- More lines here --&gt;
&lt;!-- The example below shows how form post can be used in plugin --&gt;
&lt;h3&gt;Form Post Test (HTTP POST)&lt;/h3&gt;
&lt;div class=&quot;ui form&quot;&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label for=&quot;postNameInput&quot;&gt;Name:&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;postNameInput&quot; placeholder=&quot;Your name&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label for=&quot;postAgeInput&quot;&gt;Age:&lt;/label&gt;
&lt;input type=&quot;number&quot; id=&quot;postAgeInput&quot; placeholder=&quot;Your age&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label&gt;Gender:&lt;/label&gt;
&lt;div class=&quot;ui checkbox&quot;&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;genderMale&quot; name=&quot;gender&quot; value=&quot;Male&quot;&gt;
&lt;label for=&quot;genderMale&quot;&gt;Male&lt;/label&gt;
&lt;/div&gt;
&lt;div class=&quot;ui checkbox&quot;&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;genderFemale&quot; name=&quot;gender&quot; value=&quot;Female&quot;&gt;
&lt;label for=&quot;genderFemale&quot;&gt;Female&lt;/label&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;button class=&quot;ui button primary&quot; id=&quot;postRequestButton&quot;&gt;Send&lt;/button&gt;
&lt;div class=&quot;ui message&quot; id=&quot;postResponseMessage&quot; style=&quot;display: none;&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
</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 &quot;X-CSRF-Token&quot; and the value is taken from the head
// meta tag content (i.e. &lt;meta name=&quot;zoraxy.csrf.Token&quot; content=&quot;{{.csrfToken}}&quot;&gt;)
$.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">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;!-- CSRF token, if your plugin need to make POST request to backend --&gt;
&lt;meta name=&quot;zoraxy.csrf.Token&quot; content=&quot;{{.csrfToken}}&quot;&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;/script/semantic/semantic.min.css&quot;&gt;
&lt;script src=&quot;/script/jquery-3.6.0.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;/script/semantic/semantic.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;/script/utils.js&quot;&gt;&lt;/script&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;/main.css&quot;&gt;
&lt;title&gt;RESTful Example&lt;/title&gt;
&lt;style&gt;
body {
background-color: var(--theme_bg_primary);
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;!-- Dark theme script must be included after body tag--&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;/darktheme.css&quot;&gt;
&lt;script src=&quot;/script/darktheme.js&quot;&gt;&lt;/script&gt;
&lt;br&gt;
&lt;div class=&quot;standardContainer&quot;&gt;
&lt;div class=&quot;ui container&quot;&gt;
&lt;h1&gt;RESTFul API Example&lt;/h1&gt;
&lt;!-- The example below show how HTTP GET is used with Zoraxy plugin --&gt;
&lt;h3&gt;Echo Test (HTTP GET)&lt;/h3&gt;
&lt;div class=&quot;ui form&quot;&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label for=&quot;nameInput&quot;&gt;Enter your name:&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;nameInput&quot; placeholder=&quot;Your name&quot;&gt;
&lt;/div&gt;
&lt;button class=&quot;ui button primary&quot; id=&quot;sendRequestButton&quot;&gt;Send Request&lt;/button&gt;
&lt;div class=&quot;ui message&quot; id=&quot;responseMessage&quot; style=&quot;display: none;&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
document.getElementById('sendRequestButton').addEventListener('click', function() {
const name = document.getElementById('nameInput').value;
if (name.trim() === &quot;&quot;) {
alert(&quot;Please enter a name.&quot;);
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();
}
});
});
&lt;/script&gt;
&lt;!-- The example below shows how form post can be used in plugin --&gt;
&lt;h3&gt;Form Post Test (HTTP POST)&lt;/h3&gt;
&lt;div class=&quot;ui form&quot;&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label for=&quot;postNameInput&quot;&gt;Name:&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;postNameInput&quot; placeholder=&quot;Your name&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label for=&quot;postAgeInput&quot;&gt;Age:&lt;/label&gt;
&lt;input type=&quot;number&quot; id=&quot;postAgeInput&quot; placeholder=&quot;Your age&quot;&gt;
&lt;/div&gt;
&lt;div class=&quot;field&quot;&gt;
&lt;label&gt;Gender:&lt;/label&gt;
&lt;div class=&quot;ui checkbox&quot;&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;genderMale&quot; name=&quot;gender&quot; value=&quot;Male&quot;&gt;
&lt;label for=&quot;genderMale&quot;&gt;Male&lt;/label&gt;
&lt;/div&gt;
&lt;div class=&quot;ui checkbox&quot;&gt;
&lt;input type=&quot;checkbox&quot; id=&quot;genderFemale&quot; name=&quot;gender&quot; value=&quot;Female&quot;&gt;
&lt;label for=&quot;genderFemale&quot;&gt;Female&lt;/label&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;button class=&quot;ui button primary&quot; id=&quot;postRequestButton&quot;&gt;Send&lt;/button&gt;
&lt;div class=&quot;ui message&quot; id=&quot;postResponseMessage&quot; style=&quot;display: none;&quot;&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;script&gt;
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() === &quot;&quot; || age.trim() === &quot;&quot; || (!genderMale &amp;&amp; !genderFemale)) {
alert(&quot;Please fill out all fields.&quot;);
return;
}
const gender = genderMale ? &quot;Male&quot; : &quot;Female&quot;;
// .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 &quot;X-CSRF-Token&quot; and the value is taken from the head
// meta tag content (i.e. &lt;meta name=&quot;zoraxy.csrf.Token&quot; content=&quot;{{.csrfToken}}&quot;&gt;)
$.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();
}
});
});
&lt;/script&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</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 (
&quot;embed&quot;
_ &quot;embed&quot;
&quot;encoding/json&quot;
&quot;fmt&quot;
&quot;net/http&quot;
&quot;strconv&quot;
plugin &quot;example.com/zoraxy/restful-example/mod/zoraxy_plugin&quot;
)
const (
PLUGIN_ID = &quot;com.example.restful-example&quot;
UI_PATH = &quot;/&quot;
WEB_ROOT = &quot;/www&quot;
)
//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(&amp;plugin.IntroSpect{
ID: &quot;com.example.restful-example&quot;,
Name: &quot;Restful Example&quot;,
Author: &quot;foobar&quot;,
AuthorContact: &quot;admin@example.com&quot;,
Description: &quot;A simple demo for making RESTful API calls in plugin&quot;,
URL: &quot;https://example.com&quot;,
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 &quot;/&quot;
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, &amp;content, WEB_ROOT, UI_PATH)
embedWebRouter.RegisterTerminateHandler(func() {
// Do cleanup here if needed
fmt.Println(&quot;Restful-example Exited&quot;)
}, 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(&quot;/api/echo&quot;, 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(&quot;name&quot;)
if name == &quot;&quot; {
http.Error(w, &quot;Missing 'name' query parameter&quot;, http.StatusBadRequest)
return
}
w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
response := map[string]string{&quot;message&quot;: fmt.Sprintf(&quot;Hello %s&quot;, name)}
if err := json.NewEncoder(w).Encode(response); err != nil {
http.Error(w, &quot;Failed to encode response&quot;, 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(&quot;/api/post&quot;, func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, &quot;Invalid request method&quot;, http.StatusMethodNotAllowed)
return
}
if err := r.ParseForm(); err != nil {
http.Error(w, &quot;Failed to parse form data&quot;, http.StatusBadRequest)
return
}
for key, values := range r.PostForm {
for _, value := range values {
// Generate a simple HTML response
w.Header().Set(&quot;Content-Type&quot;, &quot;text/html&quot;)
fmt.Fprintf(w, &quot;%s: %s&lt;br&gt;&quot;, key, value)
}
}
}, nil)
// Serve the restful-example page in the www folder
http.Handle(UI_PATH, embedWebRouter.Handler())
fmt.Println(&quot;Restful-example started at http://127.0.0.1:&quot; + strconv.Itoa(runtimeCfg.Port))
err = http.ListenAndServe(&quot;127.0.0.1:&quot;+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>