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

14 KiB

HTTP Client Examples

Complete, runnable examples for various languages.

Bash / curl

Complete Script with Auto-Login

#!/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

#!/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

#!/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

// 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

// 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)
}