Files
siyuan/kernel/util/net.go
Yingyi / 颖逸 e564ce7b1f Support kernel plugin system (#17487)
* ♻️ Add/update indirect Go dependencies in kernel

Update kernel/go.mod and kernel/go.sum to add multiple indirect modules and checksum entries. Notable additions include github.com/fastschema/qjs, github.com/filecoin-project/go-jsonrpc, github.com/ipfs/go-log/v2, go.opencensus.io, go.uber.org/{atomic,multierr,zap}, golang.org/x/xerrors and github.com/golang/groupcache among many transitive entries. Changes ensure transitive dependencies are pinned and go.sum checksums are present (likely produced by `go mod tidy`) to make builds reproducible.

* refactor: export bazaar.GetCurrentBackend for kernel plugin platform matching

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* build: promote qjs to direct dependency for kernel plugin system

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugin): add KernelPlugin struct with QJS runtime lifecycle and state machine

Introduces plugin/plugin.go with KernelPlugin owning an isolated QuickJS
runtime, a mutex-serialized call path, RPC method registration/dispatch,
Promise awaiting, JSON round-trip result conversion, and WebSocket tracking.
Adds sandbox_stub.go as a temporary no-op stub for injectSandboxGlobals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugin): add PluginManager singleton for kernel plugin discovery and lifecycle

* feat(plugin): add sandbox injection scaffold with siyuan.log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugin): implement siyuan.storage CRUD scoped to petal storage directory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugin): implement siyuan.fetch with browser-like Response interface

* feat(plugin): implement siyuan.socket with browser-compatible WebSocket API

- Add sync import for mutex-protected WebSocket connection tracking
- Implement __siyuan_socket Go function that creates browser-compatible WebSocket objects
- Support send() method with queueing for messages sent before connection opens
- Support close() method for closing the WebSocket connection
- Track connection state via readyState property (0=CONNECTING, 1=OPEN, 3=CLOSED)
- Connect to kernel WebSocket endpoint with automatic auth token injection
- Run WebSocket I/O in background goroutine with proper cleanup
- Wire up siyuan.socket JS API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugin): implement siyuan.rpc.register for JSON-RPC method registration

* feat(plugin): add JSON-RPC 2.0 handler for kernel plugin method dispatch

* feat(plugin): register /api/plugin/rpc/:name and /ws/plugin/rpc/:name routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(plugin): wire kernel plugin manager start/stop into main lifecycle

* feat(plugin): hook SetPetalEnabled to start/stop kernel plugins on enable/disable

* test(plugin): add unit tests for kernel plugin state machine and eligibility

* test(plugin): add comprehensive unit tests for manager, sandbox, and RPC handlers

* refactor(plugin): Export IsTargetSupported and update usages

Rename isTargetSupported to exported IsTargetSupported and adjust its comment. Replace local calls with bazaar.IsTargetSupported in kernel/bazaar and kernel/plugin/manager, removing the duplicated isKernelEligible helper. Update tests to import bazaar, call the new function, and change expectations to reflect that nil/empty kernel slices are treated as supported (i.e. supported on all platforms).

* refactor(plugin): initialize PluginManager in main and update related usages

* refactor(plugin): update JWT handling and plugin initialization for kernel plugins

* refactor(plugin): enhance plugin initialization and improve sandbox global injections

* refactor(kernel-plugin): Refactor plugin RPC registration and sandbox integration

- Removed deprecated tests and refactored existing tests for clarity and efficiency.
- Updated RPC method registration to use `bind` and `unbind` methods for better clarity.
- Enhanced the `injectSandboxGlobals` function to include additional properties for the plugin.
- Improved error handling in RPC methods and ensured proper state management for plugins.
- Added benchmarks for map to JS conversion performance.
- Cleaned up unused imports and organized code structure for better readability.

* refactor(plugin): enhance concurrency handling and improve WebSocket integration

* refactor(kernel-plugin): enhance RPC method handling and improve function registration

* feat(kernel-plugin): add RPC method info retrieval and enhance plugin management

* refactor(plugin): add plugin management endpoints and enhance plugin info retrieval

* refactor(kernel-plugin): enhance RPC method handling and improve plugin info retrieval

* refactor(kernel-plugin): improve error handling and response structures in RPC methods

* refactor(kernel-plugin): improve error handling in RPC methods and enhance WebSocket closure management

* fix(kernel-plugin): initialize sockets and socketMus maps in NewKernelPlugin

* feat(kernel-plugin): add wsWrite helper and fix PushNotification omitempty

Add wsWrite method on KernelPlugin that acquires the per-connection write
mutex before sending a text frame, returning nil for untracked connections.
Fix PushNotification's Params field to use omitempty for JSON-RPC 2.0 §4.2
compliance. Add rpc_test.go with newTestWsPair helper and tests for wsWrite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(kernel-plugin): add BroadcastNotification and per-connection write mutex

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(kernel-plugin): expose siyuan.rpc.broadcast in plugin sandbox

Add rpc.broadcast(method, params) binding in injectRpc so JS plugins
can push JSON-RPC 2.0 notifications to all connected server clients.
Fix deadlock by introducing a dedicated socketsMu RWMutex for the
sockets map, decoupling socket tracking from the main plugin mutex
that is held during Start()/Eval().

* fix(kernel-plugin): double-unlock in send handler and document PushNotification write-safety

Remove spurious mu.Unlock() inside the nil-conn branch of injectSocket's
CONNECTING-state send handler; the outer unconditional unlock is sufficient,
so the inner one causes a panic under concurrent load.

Document that PushNotification bypasses per-connection write serialization
and must not be called concurrently with BroadcastNotification/wsWrite on
the same connection without external locking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* style(kernel-plugin): align struct field declarations in KernelPlugin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(kernel-plugin): omit params field from JsonRpcRequest when nil (JSON-RPC 2.0 §4.1)

Per spec, params MAY be omitted; add omitempty so marshaled requests
with no parameters do not emit "params":null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(kernel-plugin): change JsonRpcRequest.Params to *json.RawMessage

A pointer correctly models the three-way distinction:
- nil      → params key absent (omitted from marshal output via omitempty)
- non-nil → params present (null, array, or object)

The previous []byte omitempty omitted the key only for nil/empty slices
and could not distinguish absent from explicit null on the wire.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(kernel-plugin): unify method naming conventions and improve JSON-RPC request handling

* fix(kernel-plugin): improve WebSocket message handling and ensure thread safety with mutexes

* fix(kernel-plugin): enhance WebSocket handling and improve error management in storage methods

* fix(kernel-plugin): rename JsonRpcRequestRaw to JsonRpcInboundRequest and update related methods

* fix(kernel-plugin): improve plugin management and error handling in kernel plugin methods

* fix(kernel-plugin): rename kernel field to kernels and update related references

* feat(kernel-plugin): implement logging and improve concurrency handling in plugin manager and storage methods

* feat(kernel-plugin): enhance RPC parameter handling and add JSON array parsing support

* refactor(kernel-plugin): refactor RPC handling and improve logging functionality

* refactor(kernel-plugin): streamline loggerWrapper function and improve error handling in injectFetch

* refactor(kernel-plugin): optimize injectFetch function and enhance error handling

* feat(kernel-plugin): add onLoaded hook and enhance plugin lifecycle management

* feat(kernel-plugin): add ObjectFreeze and ObjectSeal functions to enhance API security

* feat(kernel-plugin): add InitJwtKey function to generate JWT signing key

* refactor(kernel-plugin): enhance error handling and logging in plugin lifecycle methods

* feat(kernel-plugin): improve WebSocket error handling and add concurrency support in BroadcastNotification

* feat(kernel-plugin): enhance error handling in storage and fetch methods with panic recovery

* feat(kernel-plugin): enhance PluginManager concurrency and error handling with sync.Map and atomic operations

* feat(kernel-plugin): refactor PluginState to use atomic operations for improved concurrency

* feat(kernel-plugin): add PluginStateLoaded and update state management in plugin lifecycle

* refactor(kernel-plugin): update logging level in loadPetals and refactor loggerWrapper return values

* feat(kernel-plugin): simplify invokeHook and enhance error handling in Object methods

* feat(kernel-plugin): remove obsolete test files for plugin functionality

* refactor(kernel-plugin): implement loggerWrapper and rpcParamsToJsValue functions for improved logging and RPC parameter handling

* feat(kernel-plugin): introduce Worker for serializing plugin tasks and enhance context management

* refactor(worker): enhance task execution with callback support and graceful shutdown

- Introduced a callback mechanism in the Task struct to handle results and errors.
- Updated the Run method to accept a callback, allowing immediate handling of task results.
- Added a RunSync method for synchronous task execution with result retrieval.
- Implemented atomic closure state management to prevent task submission after closure.
- Enhanced the Close method to ensure graceful shutdown and wait for the worker to finish processing.

* feat(kernel-plugin): refactor storage and RPC methods to use PromiseRun for better error handling

* feat(kernel-plugin): enhance plugin event handling with lifecycle and RPC event subscriptions

* refactor(kernel-plugin): replace PromiseRun with worker.Run for improved error handling in event and storage methods

* chore(kernel-plugin): add goja dependency, drop qjs

* chore(kernel-plugin): delete KernelPluginLogger (qjs stdout/stderr only)

* refactor(kernel-plugin): replace qjs runtime with goja in plugin.go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(kernel-plugin): add sandbox utility tests (pre-rewrite)

* refactor(kernel-plugin): rewrite sandbox utility functions for goja

Replace goValueToJsValue, getJsContextValue, dispatchEvent with goja
implementations; add convertJsonNumbers helper; stub ObjectFreeze and
ObjectSeal as no-ops; delete dead qjs-only helpers (invokeRpcMethod,
PromiseAwait, rpcParamsToJsValue, parseJsonArrayStringToJsValueArray,
parseJsonStringToJsValue, loggerWrapper, ObjectSetDataMethods).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(kernel-plugin): rewrite sandbox.go inject functions for goja

Replace all qjs-based inject functions (injectGlobalContext, injectPlugin,
injectLogger, injectEvent, injectStorage, injectFetch, injectSocket, injectRpc)
with goja equivalents. Add ObjectSetDataMethods and loggerWrapper helpers.
Remove all remaining qjs dead code; ObjectFreeze/ObjectSeal now call
Object.freeze/seal via goja AssertFunction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(kernel-plugin): add plugin lifecycle and RPC integration tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(kernel-plugin): go mod tidy after qjs removal

Remove fastschema/qjs from go.mod and go.sum, add go-sourcemap as
indirect (transitive dep of dop251/goja), mark go-sourcemap indirect.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix(kernel-plugin): fix invokeHook early-return on subscribe failure, safe await extraction, and goja value cross-goroutine access in socket methods

* refactor(kernel-plugin): replace goValueToJsValue with goValueToJsValueSafely in sandbox functions and tests

* feat(plugin): enhance plugin management and error handling

- Added GetLoadedPlugin method to retrieve loaded plugin info by name.
- Introduced file path for kernel.js in KernelPlugin struct.
- Updated Eval method to use the new file path for script execution.
- Improved error handling in injectGlobalContext and other injection functions using recover.
- Refactored task execution in Worker to use clearer types for task executors and callbacks.
- Enhanced storage methods to ensure proper error handling and logging.
- Updated loggerWrapper to handle errors more gracefully.
- Ensured consistent use of error handling patterns across various plugin methods.

* refactor(worker): enhance task execution with goja runtime integration

- Updated TaskExecutor and TaskCallback signatures to accept *goja.Runtime.
- Modified Worker to start processing tasks with an event loop.
- Improved error handling in task execution to catch panics from both executor and callback.
- Renamed Close method to Stop for clarity on worker shutdown behavior.

* refactor(kernel-plugin): streamline worker implementation and update context handling in plugin methods

* refactor(kernel-plugin): update event handler to use byte slices and improve event dispatching

* refactor(worker): simplify RunSync method by removing unnecessary select statement

* refactor(kernel-plugin): enhance plugin lifecycle management and improve RPC method binding

* refactor(kernel-plugin): improve error logging in data methods for better debugging

* refactor(kernel-plugin): add version field to plugin data structures and update related methods

* refactor(kernel-plugin): replace JsonRpcInboundRequest with JsonRpcRequest and update related methods

* refactor(kernel-plugin): enhance plugin lifecycle hooks and improve RPC method invocation

* feat(kernel-plugin): improve error handling and response processing in fetch and socket methods

* refactor(kernel-plugin): update invokeFunction to handle promise results correctly

* refactor(kernel-plugin): streamline event handling and remove unused JSON marshaling functions

* refactor(kernel-plugin): improve error handling in start method and add event publishing for lifecycle states

* refactor(kernel-plugin): move logging to separate function and execute in goroutines for improved performance

* feat(kernel-plugin): add unique ID generation for start and stop events

* refactor(kernel-plugin): enhance error handling and concurrency in storage operations

Co-authored-by: Copilot <copilot@github.com>

* fix(kernel-plugin): remove unexpected resolve in fetch function

* feat(kernel-plugin): enhance JSON-RPC request handling with optional parameters and improved error reporting

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): rename await to async in dispatchEvent function for clarity

Co-authored-by: Copilot <copilot@github.com>

* fix(kernel-plugin): improve error handling in RPC method execution and hook invocation

* feat(kernel-plugin): implement custom JSON marshaling for JsonRpcRequest to handle optional parameters

* feat(kernel-plugin): add error codes for plugin state and improve error handling in RPC responses

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): clean up context usage and improve error logging for RPC methods

* feat(kernel-plugin): add buffer method to object for asynchronous data processing

* fix(kernel-plugin): Fixed the problem of blocking when plug-in life cycle function is not bound

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): implement public and private web server handlers and enhance request handling

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): enhance server request handling and introduce server handler invocation

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): enhance response handling and add jsValueToBytes conversion utility

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): comment out public web server route in router

* feat(kernel-plugin): add WebSocket and EventSource proxy handlers and update sandbox integration

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): implement HTTP proxy handler with response header forwarding

* refactor(kernel-plugin): refactor siyuan.client.* methods

* feat(kernel-plugin): add support for EventSource with SSE handling and response header forwarding

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): add SSE support using r3labs/sse library for EventSource handling

* feat(kernel-plugin): enhance SSE client with onclose event handling

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): implement SSE event handling and error management in server-sent events

* feat(kernel-plugin): refactor SSE handling and introduce request handler utility functions

Co-authored-by: Copilot <copilot@github.com>

* feat(kernel-plugin): enhance WebSocket message handling with buffered amount tracking and cleanup

Co-authored-by: Copilot <copilot@github.com>

* perf(kernel-plugin): improve WebSocket message handling with channel-based message sending and error management

Co-Authored-By: Copilot <copilot@github.com>

* refactor(kernel-plugin): remove invokeServerHandler

Co-Authored-By: Copilot <copilot@github.com>

* feat(kernel-plugin): implement WebSocket message handling with improved structure and error management

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): Refactor code structure for improved readability and maintainability

* refactor(kernel-plugin): streamline HTTP client creation and enhance event source state management

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): enhance WebSocket and SSE handling with improved closure management and error handling

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): optimize WebSocket handling by restructuring state management and improving closure logic

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): simplify header setting and improve null checks in WebSocket and SSE handling

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): update WebSocket request handling to improve error management and consistency

* refactor(kernel-plugin): improve WebSocket error handling by adding close message management

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): Refactor WebSocket handling to use gws library

- Replaced gorilla/websocket with lxzan/gws for WebSocket connections.
- Introduced gwsEventHandler to manage WebSocket events with customizable callbacks.
- Updated KernelPlugin to track gws connections and handle message broadcasting.
- Refactored RPC WebSocket handling to accommodate new gws structure.
- Simplified message sending and connection management logic.
- Added utility function to check for undefined JavaScript values.

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): integrate gws library for improved WebSocket handling and error management

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): remove unnecessary error handling in WebSocket request processing

* refactor(kernel-plugin): enhance error logging in WebSocket message handling

Co-Authored-By: Copilot <copilot@github.com>

* refactor(kernel-plugin): replace gwsEventHandler with WsEventHandler and improve WebSocket management

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): integrate chanx for improved event handling in SSE

* refactor(kernel-plugin): update handleHttpRequest signature to include gin.Context for improved request handling

Co-authored-by: Copilot <copilot@github.com>

* refactor(kernel-plugin): optimize WebSocket connection management with context and sync mechanisms

* refactor(kernel-plugin): improve error handling and context management in WebSocket and HTTP request handling

* refactor(kernel-plugin): enhance WebSocket management with context handling and improved error reporting

* fix(kernel-plugin): streamline header export and enhance error handling in injectClient function

Co-authored-by: Copilot <copilot@github.com>

* perf(kernel-plugin): enhance httpProxy and esProxy functions with improved error handling and content management

Co-authored-by: Copilot <copilot@github.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <copilot@github.com>
2026-05-09 11:26:37 +08:00

381 lines
9.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package util
import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/gin-gonic/gin"
"github.com/imroc/req/v3"
"github.com/siyuan-note/httpclient"
"github.com/siyuan-note/logging"
)
// GetPrivateIPv4s 获取本地所有的私有 IPv4 地址(排除虚拟网卡)
func GetPrivateIPv4s() (ret []string) {
ret = []string{}
interfaces, err := net.Interfaces()
if err != nil {
return
}
// 常见的虚拟网卡名称关键字黑名单
virtualKeywords := []string{"docker", "veth", "br-", "vmnet", "vbox", "utun", "tun", "tap", "bridge", "cloud", "hyper-"}
for _, itf := range interfaces {
// 1. 基础状态过滤:必须是启动状态且不能是回环网卡
if itf.Flags&net.FlagUp == 0 || itf.Flags&net.FlagLoopback != 0 {
continue
}
// 2. 硬件地址过滤:物理网卡通常必须有 MAC 地址
if len(itf.HardwareAddr) == 0 {
continue
}
// 3. 名称过滤:排除已知虚拟网卡前缀
name := strings.ToLower(itf.Name)
isVirtual := false
for _, kw := range virtualKeywords {
if strings.Contains(name, kw) {
isVirtual = true
break
}
}
if isVirtual {
continue
}
// 4. 提取并校验 IP
addrs, err := itf.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
// 仅保留 IPv4 且必须是私有局域网地址 (10.x, 172.16.x, 192.168.x)
if ip.To4() != nil && ip.IsPrivate() {
ret = append(ret, ip.String())
}
}
}
return
}
func IsLocalHostname(hostname string) bool {
if "localhost" == hostname || strings.HasSuffix(hostname, ".localhost") {
return true
}
if ip := net.ParseIP(hostname); nil != ip {
return ip.IsLoopback()
}
return false
}
func IsLocalHost(host string) bool {
if hostname, _, err := net.SplitHostPort(strings.TrimSpace(host)); err != nil {
return false
} else {
return IsLocalHostname(hostname)
}
}
func IsLocalOrigin(origin string) bool {
if u, err := url.Parse(origin); err == nil {
return IsLocalHostname(u.Hostname())
}
return false
}
func IsOnline(checkURL string, skipTlsVerify bool, timeout int) bool {
if "" == checkURL {
return false
}
u, err := url.Parse(checkURL)
if err != nil {
logging.LogWarnf("invalid check URL [%s]", checkURL)
return false
}
if u.Scheme == "file" {
filePath := strings.TrimPrefix(checkURL, "file://")
_, err := os.Stat(filePath)
return err == nil
}
if isOnline(checkURL, skipTlsVerify, timeout) {
return true
}
logging.LogWarnf("network is offline [checkURL=%s]", checkURL)
return false
}
func IsPortOpen(port string) bool {
timeout := time.Second
conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout)
if err != nil {
return false
}
if nil != conn {
conn.Close()
return true
}
return false
}
func isOnline(checkURL string, skipTlsVerify bool, timeout int) (ret bool) {
c := req.C().
SetTimeout(time.Duration(timeout) * time.Millisecond).
SetProxy(httpclient.ProxyFromEnvironment).
SetUserAgent(UserAgent)
if skipTlsVerify {
c.EnableInsecureSkipVerify()
}
for i := 0; i < 2; i++ {
resp, err := c.R().Get(checkURL)
if resp.GetHeader("Location") != "" {
return true
}
var urlErr *url.Error
if errors.As(err, &urlErr) && urlErr.URL != checkURL {
// DNS 重定向
logging.LogWarnf("network is online [DNS redirect, checkURL=%s, retURL=%s]", checkURL, urlErr.URL)
return true
}
ret = err == nil
if ret {
break
}
logging.LogWarnf("check url [%s] is online failed: %s", checkURL, err)
time.Sleep(1 * time.Second)
}
return
}
func GetRemoteAddr(req *http.Request) string {
ret := req.Header.Get("X-forwarded-for")
ret = strings.TrimSpace(ret)
if "" == ret {
ret = req.Header.Get("X-Real-IP")
}
ret = strings.TrimSpace(ret)
if "" == ret {
return req.RemoteAddr
}
return strings.Split(ret, ",")[0]
}
func JsonArg(c *gin.Context, result *gulu.Result) (arg map[string]any, ok bool) {
arg = map[string]any{}
if err := c.ShouldBindJSON(&arg); err != nil {
result.Code = -1
var detail string
if errors.Is(err, io.EOF) {
detail = "the request body is empty or truncated (EOF)"
} else {
detail = err.Error()
}
result.Msg = fmt.Sprintf("Parses request [%s] failed: %s", c.Request.URL.Path, detail)
return
}
ok = true
return
}
// GetRequestUrlStringParam extracts a string parameter from URL (path or query parameters).
func GetRequestUrlStringParam(c *gin.Context, key string) string {
// /path/:name
if value := c.Param(key); value != "" {
return value
}
// /path?name=xxx
if value := c.Query(key); value != "" {
return value
}
return ""
}
// GetRequestStringParam extracts a string parameter from the request (URL or JSON body), with validation and error handling.
func GetRequestStringParam(c *gin.Context, key string, result *gulu.Result) string {
// /path/:name
if value := GetRequestUrlStringParam(c, key); value != "" {
return value
}
// /path with JSON body {key: "xxx"}
arg, ok := JsonArg(c, result)
if !ok {
return ""
}
if arg[key] == nil {
result.Code = -2
result.Msg = fmt.Sprintf("Request body prop [%s] does not exist", key)
return ""
}
value, ok := arg[key].(string)
if !ok {
result.Code = -3
result.Msg = fmt.Sprintf("Request body prop [%s] is not a string", key)
return ""
}
return value
}
// ParseJsonArg 使用泛型从 JSON 参数中提取指定键的值。
// - 如果 required 为 true 但参数缺失,则会在 ret.Msg 中说明需要传入的键
// - 如果 rejectEmpty 为 true 但参数值为空,则会在 ret.Msg 中说明该键必须不为空(字符串去空白后、空数组、无任何键的对象)
// - 如果参数存在但类型不匹配,则会在 ret.Msg 中说明该键期望的类型
// - 返回值 ok 为 false 时,表示提取失败、类型不匹配或不满足非空约束
func ParseJsonArg[T any](key string, arg map[string]any, ret *gulu.Result, required, rejectEmpty bool) (value T, ok bool) {
raw, exists := arg[key]
if !exists || raw == nil {
if required {
ret.Code = -1
ret.Msg = fmt.Sprintf("Field [%s] is required", key)
} else {
ok = true
}
return
}
value, ok = raw.(T)
if !ok {
var zero T
ret.Code = -1
// 返回对应的 JSON 类型
jsonType := ""
switch any(zero).(type) {
case string:
jsonType = "String"
case float64:
jsonType = "Number"
case bool:
jsonType = "Boolean"
case []any:
jsonType = "Array"
case map[string]any:
jsonType = "Object"
default:
jsonType = fmt.Sprintf("%T", zero)
}
ret.Msg = fmt.Sprintf("Field [%s] should be of type [%s]", key, jsonType)
return
}
if rejectEmpty {
var bad bool
switch x := any(value).(type) {
case string:
if t := strings.TrimSpace(x); t == "" {
bad = true
} else {
value = any(t).(T)
}
case []any:
bad = len(x) == 0
case map[string]any:
bad = len(x) == 0
}
if bad {
ret.Code = -1
ret.Msg = fmt.Sprintf("Field [%s] must not be empty", key)
ok = false
}
}
return
}
// JsonArgParseFunc 为单次提取函数,用于 ParseJsonArgs 批量提取。
type JsonArgParseFunc func(arg map[string]any, ret *gulu.Result) bool
// BindJsonArg 创建一个提取函数:从 arg 取 key 并写入 dest供 ParseJsonArgs 使用。
func BindJsonArg[T any](key string, dest *T, required, rejectEmpty bool) JsonArgParseFunc {
return func(arg map[string]any, ret *gulu.Result) bool {
v, ok := ParseJsonArg[T](key, arg, ret, required, rejectEmpty)
if !ok {
return false
}
*dest = v
return true
}
}
// ParseJsonArgs 按顺序执行多个提取函数。
// - 任一失败返回 false 并在 ret 中写入错误信息
// - 全部成功返回 true
func ParseJsonArgs(arg map[string]any, ret *gulu.Result, extractors ...JsonArgParseFunc) bool {
for _, ext := range extractors {
if !ext(arg, ret) {
return false
}
}
return true
}
func InvalidIDPattern(idArg string, result *gulu.Result) bool {
if ast.IsNodeIDPattern(idArg) {
return false
}
result.Code = -1
result.Msg = "invalid ID argument"
return true
}
func initHttpClient() {
http.DefaultClient = httpclient.GetCloudFileClient2Min()
http.DefaultTransport = httpclient.NewTransport(false)
}
func ParsePort(portString string) (uint16, error) {
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
logging.LogErrorf("parse port [%s] failed: %s", portString, err)
return 0, err
}
return uint16(port), nil
}