mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-05 20:58:28 +02:00
Added restful-api plugin
- Added restful-api example plugin - Added more plugin docs - Added zoraxy_plugin HandleFunc API
This commit is contained in:
787
docs/plugins/html/3. Basic Examples/2. RESTful Example.html
Normal file
787
docs/plugins/html/3. Basic Examples/2. RESTful Example.html
Normal file
@@ -0,0 +1,787 @@
|
||||
<!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>
|
Reference in New Issue
Block a user