Files
flvx/skills/flvx-api/references/examples/http-client.md
T
2026-02-28 14:07:54 +08:00

604 lines
14 KiB
Markdown

# HTTP Client Examples
Complete, runnable examples for various languages.
## Bash / curl
### Complete Script with Auto-Login
```bash
#!/bin/bash
set -e
# Configuration
BASE_URL="${FLVX_BASE_URL:?FLVX_BASE_URL not set}"
USERNAME="${FLVX_USERNAME:?FLVX_USERNAME not set}"
PASSWORD="${FLVX_PASSWORD:?FLVX_PASSWORD not set}"
# Login and get token
echo "Logging in..."
LOGIN_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/user/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"${USERNAME}\",\"password\":\"${PASSWORD}\"}")
TOKEN=$(echo "$LOGIN_RESPONSE" | jq -r '.data.token // empty')
if [ -z "$TOKEN" ]; then
echo "Login failed: $(echo "$LOGIN_RESPONSE" | jq -r '.msg')"
exit 1
fi
echo "Logged in successfully"
# API call helper
api_call() {
local endpoint="$1"
local data="${2:-{}}"
curl -s -X POST "${BASE_URL}${endpoint}" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "$data"
}
# Examples
echo "=== My Package Info ==="
api_call "/api/v1/user/package" | jq '.'
echo -e "\n=== Node List ==="
api_call "/api/v1/node/list" '{}' | jq '.data.list[] | {name, status: (.status == 1)}'
echo -e "\n=== Forward List ==="
api_call "/api/v1/forward/list" '{}' | jq '.data.list[] | {name, tunnel: .tunnel_name, port: .in_port, target: .remote_addr}'
```
### Create Forward Script
```bash
#!/bin/bash
BASE_URL="${FLVX_BASE_URL}"
TOKEN="${FLVX_TOKEN}" # Pre-obtained token
create_forward() {
local name="$1"
local tunnel_id="$2"
local remote_addr="$3"
curl -s -X POST "${BASE_URL}/api/v1/forward/create" \
-H "Authorization: ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${name}\",
\"tunnelId\": ${tunnel_id},
\"remoteAddr\": \"${remote_addr}\",
\"strategy\": \"fifo\"
}" | jq '.'
}
# Usage: ./create-forward.sh "my-web" 1 "192.168.1.100:80"
create_forward "$@"
```
---
## Python
### Complete Client Class
```python
#!/usr/bin/env python3
"""FLVX API Client"""
import os
import requests
from typing import Optional, Any, Dict, List
class FlvxError(Exception):
"""FLVX API Error"""
def __init__(self, code: int, message: str):
self.code = code
self.message = message
super().__init__(message)
class FlvxClient:
"""FLVX API Client with auto-login"""
def __init__(
self,
base_url: Optional[str] = None,
username: Optional[str] = None,
password: Optional[str] = None
):
self.base_url = base_url or os.environ.get("FLVX_BASE_URL")
self.username = username or os.environ.get("FLVX_USERNAME")
self.password = password or os.environ.get("FLVX_PASSWORD")
if not all([self.base_url, self.username, self.password]):
raise ValueError("Missing credentials. Set FLVX_BASE_URL, FLVX_USERNAME, FLVX_PASSWORD")
self.token: Optional[str] = None
def _login(self) -> None:
"""Authenticate and store token"""
resp = requests.post(
f"{self.base_url}/api/v1/user/login",
headers={"Content-Type": "application/json"},
json={"username": self.username, "password": self.password}
)
result = resp.json()
if result["code"] != 0:
raise FlvxError(result["code"], result["msg"])
self.token = result["data"]["token"]
def _headers(self) -> Dict[str, str]:
"""Get request headers with auth"""
headers = {"Content-Type": "application/json"}
if self.token:
headers["Authorization"] = self.token # NO "Bearer " prefix!
return headers
def request(self, endpoint: str, data: Any = None) -> Any:
"""Make authenticated API request"""
if not self.token:
self._login()
resp = requests.post(
f"{self.base_url}{endpoint}",
headers=self._headers(),
json=data or {}
)
result = resp.json()
if result["code"] == 0:
return result.get("data")
if result["code"] == 401:
# Token expired, retry once
self.token = None
return self.request(endpoint, data)
raise FlvxError(result["code"], result["msg"])
# Convenience methods
def get_package(self) -> Dict:
"""Get current user's package info"""
return self.request("/api/v1/user/package", {})
def list_nodes(self) -> List[Dict]:
"""List all nodes"""
data = self.request("/api/v1/node/list", {})
return data.get("list", [])
def list_forwards(self, keyword: str = "") -> List[Dict]:
"""List forwards"""
data = self.request("/api/v1/forward/list", {"keyword": keyword})
return data.get("list", [])
def create_forward(
self,
name: str,
tunnel_id: int,
remote_addr: str,
strategy: str = "fifo",
speed_id: int = 0
) -> Dict:
"""Create a forward"""
return self.request("/api/v1/forward/create", {
"name": name,
"tunnelId": tunnel_id,
"remoteAddr": remote_addr,
"strategy": strategy,
"speedId": speed_id
})
def pause_forward(self, forward_id: int) -> None:
"""Pause a forward"""
self.request("/api/v1/forward/pause", {"id": forward_id})
def resume_forward(self, forward_id: int) -> None:
"""Resume a forward"""
self.request("/api/v1/forward/resume", {"id": forward_id})
def delete_forward(self, forward_id: int) -> None:
"""Delete a forward"""
self.request("/api/v1/forward/delete", {"id": forward_id})
# Usage example
if __name__ == "__main__":
client = FlvxClient()
# Get package info
pkg = client.get_package()
print(f"Traffic: {pkg['inFlow'] / 1e9:.2f}GB ↑ / {pkg['outFlow'] / 1e9:.2f}GB ↓")
print(f"Quota: {pkg['flow']}GB")
# List forwards with traffic
print("\nForwards:")
for fwd in client.list_forwards():
print(f" {fwd['name']}: {fwd['in_port']} → {fwd['remote_addr']}")
print(f" Traffic: {fwd['in_flow'] / 1e9:.2f}GB ↑ / {fwd['out_flow'] / 1e9:.2f}GB ↓")
```
---
## Node.js / TypeScript
### Complete Client Class
```typescript
// flvx-client.ts
interface APIResponse<T = unknown> {
code: number;
msg: string;
data?: T;
ts: number;
}
class FlvxError extends Error {
constructor(public code: number, message: string) {
super(message);
this.name = "FlvxError";
}
}
interface UserPackage {
flow: number;
inFlow: number;
outFlow: number;
tunnels: number;
forwards: number;
expTime: number;
}
interface Node {
id: number;
name: string;
status: number;
server_ip: string;
}
interface Forward {
id: number;
name: string;
tunnel_id: number;
tunnel_name: string;
in_port: number;
remote_addr: string;
status: number;
in_flow: number;
out_flow: number;
}
class FlvxClient {
private baseUrl: string;
private username: string;
private password: string;
private token?: string;
constructor(options?: {
baseUrl?: string;
username?: string;
password?: string;
}) {
this.baseUrl = options?.baseUrl ?? process.env.FLVX_BASE_URL ?? "";
this.username = options?.username ?? process.env.FLVX_USERNAME ?? "";
this.password = options?.password ?? process.env.FLVX_PASSWORD ?? "";
if (!this.baseUrl || !this.username || !this.password) {
throw new Error("Missing credentials. Set FLVX_BASE_URL, FLVX_USERNAME, FLVX_PASSWORD");
}
}
private async login(): Promise<void> {
const res = await fetch(`${this.baseUrl}/api/v1/user/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
username: this.username,
password: this.password,
}),
});
const result: APIResponse<{ token: string }> = await res.json();
if (result.code !== 0) {
throw new FlvxError(result.code, result.msg);
}
this.token = result.data!.token;
}
private async request<T>(endpoint: string, data?: object): Promise<T> {
if (!this.token) {
await this.login();
}
const res = await fetch(`${this.baseUrl}${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: this.token!, // NO "Bearer " prefix!
},
body: JSON.stringify(data ?? {}),
});
const result: APIResponse<T> = await res.json();
if (result.code === 0) {
return result.data!;
}
if (result.code === 401) {
// Token expired, retry once
this.token = undefined;
return this.request<T>(endpoint, data);
}
throw new FlvxError(result.code, result.msg);
}
// Convenience methods
async getPackage(): Promise<UserPackage> {
return this.request("/api/v1/user/package", {});
}
async listNodes(): Promise<Node[]> {
const data = await this.request<{ list: Node[] }>("/api/v1/node/list", {});
return data.list ?? [];
}
async listForwards(keyword = ""): Promise<Forward[]> {
const data = await this.request<{ list: Forward[] }>("/api/v1/forward/list", {
keyword,
});
return data.list ?? [];
}
async createForward(options: {
name: string;
tunnelId: number;
remoteAddr: string;
strategy?: "fifo" | "round";
speedId?: number;
}): Promise<Forward> {
return this.request("/api/v1/forward/create", {
name: options.name,
tunnelId: options.tunnelId,
remoteAddr: options.remoteAddr,
strategy: options.strategy ?? "fifo",
speedId: options.speedId ?? 0,
});
}
async pauseForward(id: number): Promise<void> {
await this.request("/api/v1/forward/pause", { id });
}
async resumeForward(id: number): Promise<void> {
await this.request("/api/v1/forward/resume", { id });
}
async deleteForward(id: number): Promise<void> {
await this.request("/api/v1/forward/delete", { id });
}
}
export { FlvxClient, FlvxError };
// Usage
async function main() {
const client = new FlvxClient();
// Get package info
const pkg = await client.getPackage();
console.log(`Traffic: ${(pkg.inFlow / 1e9).toFixed(2)}GB ↑ / ${(pkg.outFlow / 1e9).toFixed(2)}GB ↓`);
console.log(`Quota: ${pkg.flow}GB`);
// List nodes
console.log("\nNodes:");
const nodes = await client.listNodes();
for (const node of nodes) {
console.log(` ${node.name}: ${node.status ? "Online" : "Offline"}`);
}
// List forwards
console.log("\nForwards:");
const forwards = await client.listForwards();
for (const fwd of forwards) {
console.log(` ${fwd.name}: ${fwd.in_port} → ${fwd.remote_addr}`);
}
}
main().catch(console.error);
```
---
## Go
### Complete Client Package
```go
// flvx/client.go
package flvx
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type Client struct {
BaseURL string
Username string
Password string
Token string
}
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data json.RawMessage `json:"data"`
TS int64 `json:"ts"`
}
type FlvxError struct {
Code int
Message string
}
func (e *FlvxError) Error() string {
return fmt.Sprintf("FLVX error %d: %s", e.Code, e.Message)
}
func NewClient() *Client {
return &Client{
BaseURL: os.Getenv("FLVX_BASE_URL"),
Username: os.Getenv("FLVX_USERNAME"),
Password: os.Getenv("FLVX_PASSWORD"),
}
}
func (c *Client) Login() error {
payload := map[string]string{
"username": c.Username,
"password": c.Password,
}
var result struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Token string `json:"token"`
} `json:"data"`
}
if err := c.request("/api/v1/user/login", payload, &result); err != nil {
return err
}
if result.Code != 0 {
return &FlvxError{Code: result.Code, Message: result.Msg}
}
c.Token = result.Data.Token
return nil
}
func (c *Client) Request(endpoint string, data interface{}, result interface{}) error {
// Auto-login if no token
if c.Token == "" {
if err := c.Login(); err != nil {
return err
}
}
return c.request(endpoint, data, result)
}
func (c *Client) request(endpoint string, data interface{}, result interface{}) error {
body, err := json.Marshal(data)
if err != nil {
return err
}
req, err := http.NewRequest("POST", c.BaseURL+endpoint, bytes.NewReader(body))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if c.Token != "" {
req.Header.Set("Authorization", c.Token) // NO "Bearer " prefix!
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
return json.Unmarshal(respBody, result)
}
// Convenience methods
func (c *Client) ListNodes() ([]map[string]interface{}, error) {
var result struct {
Code int `json:"code"`
Data struct {
List []map[string]interface{} `json:"list"`
} `json:"data"`
}
if err := c.Request("/api/v1/node/list", map[string]interface{}{}, &result); err != nil {
return nil, err
}
if result.Code != 0 {
return nil, &FlvxError{Code: result.Code, Message: "failed to list nodes"}
}
return result.Data.List, nil
}
func (c *Client) CreateForward(name string, tunnelID int, remoteAddr string) (map[string]interface{}, error) {
payload := map[string]interface{}{
"name": name,
"tunnelId": tunnelID,
"remoteAddr": remoteAddr,
"strategy": "fifo",
}
var result struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data map[string]interface{} `json:"data"`
}
if err := c.Request("/api/v1/forward/create", payload, &result); err != nil {
return nil, err
}
if result.Code != 0 {
return nil, &FlvxError{Code: result.Code, Message: result.Msg}
}
return result.Data, nil
}
// Usage example
func Example() {
client := NewClient()
nodes, err := client.ListNodes()
if err != nil {
fmt.Println("Error:", err)
return
}
for _, node := range nodes {
fmt.Printf("Node: %v (status: %v)\n", node["name"], node["status"])
}
fwd, err := client.CreateForward("my-forward", 1, "192.168.1.100:80")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Created forward: %v\n", fwd)
}
```