devbridge

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

DevBridge Go SDK

DevBridge 是华为云的开发隧道服务,通过中继连接 Host(本地服务托管)与 Connect(远端连接),使本地服务无需公网入站端口即可被远程访问。

本 SDK 把 DevBridge CLI 的核心能力封装为 Go API,支持:

  • 隧道管理:创建、查询、更新、删除隧道
  • 端口管理:创建、查询、更新、删除端口
  • 令牌签发:签发 Host / Connect 令牌
  • Host 托管:把本地端口通过 WebSocket + SSH 隧道转发到 DevBridge 中继
  • Connect 连接:连接隧道,在本地建立端口映射访问远端服务

安装

go get github.com/huaweicloud/devspace-devbridge/sdk

快速开始

完整流程:Host 托管 + Connect 连接
package main

import (
    "context"
    "log"
    "time"

    "github.com/huaweicloud/devspace-devbridge/sdk"
)

func main() {
    ctx := context.Background()
    client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
    if err != nil {
        log.Fatal(err)
    }

    // 1. 创建隧道
    tunnel, err := client.CreateTunnel(ctx, "my-tunnel", "开发联调", nil)
    if err != nil {
        log.Fatal(err)
    }

    // 2. 添加端口
    allowAnon := true
    client.CreatePort(ctx, tunnel.ID, 8080, "http", &allowAnon)

    // 3. Host 托管(在服务所在设备)
    go func() {
        client.Host(ctx, devbridge.HostConfig{
            TunnelID: tunnel.ID,
            Ports:    []int{8080},
        })
    }()

    time.Sleep(2 * time.Second)

    // 4. Connect 连接(在访问设备)
    //    连接后 http://localhost:8080 → 远端服务
    client.Connect(ctx, devbridge.ConnectConfig{
        TunnelID: tunnel.ID,
        Ports:    []int{8080},
    })
}
仅 Host 托管
client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
    if err != nil {
        log.Fatal(err)
    }

// 阻塞运行,Ctrl+C / cancel context 停止
client.Host(ctx, devbridge.HostConfig{
    TunnelID: "aaaadysa",
    Ports:    []int{8080, 3000},
})
仅 Connect 连接
client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
    if err != nil {
        log.Fatal(err)
    }

// 阻塞运行,连接后 localhost:8080 → 远端
client.Connect(ctx, devbridge.ConnectConfig{
    TunnelID: "aaaadysa",
    Ports:    []int{8080},
})
使用 JWT 令牌(跳过 API 调用)
client, err := devbridge.NewClient(devbridge.Config{})
    if err != nil {
        log.Fatal(err)
    }

// 签发 Host 令牌
token, _ := client.IssueToken(ctx, "aaaadysa", "host")

// 用令牌启动 Host
client.Host(ctx, devbridge.HostConfig{
    TunnelID: "aaaadysa",
    JWTToken: token.Token,
})

API 参考

客户端配置
client, err := devbridge.NewClient(devbridge.Config{
    APIKey:      "your-key",            // API Key
    APIBaseURL:  "custom-url",          // 自定义 API 地址
    GatewayAddr: "addr:443",            // 自定义网关地址
    GatewayHost: "host",                // 自定义网关 SNI host
})
if err != nil {
    log.Fatal(err)
}

API Key 也可通过环境变量 HW_API_KEY 设置。

隧道管理
方法 说明
CreateTunnel(ctx, name, desc, *expiration) 创建隧道
ListTunnels(ctx) 查询隧道列表
ShowTunnel(ctx, id) 查询隧道详情
UpdateTunnel(ctx, id, *name, *desc, *exp) 更新隧道
DeleteTunnel(ctx, id) 删除隧道
DeleteAllTunnels(ctx) 删除全部隧道
IssueToken(ctx, id, scope) 签发令牌(scope: "host" / "connect")
GetLimits(ctx) 查询配额
端口管理
方法 说明
CreatePort(ctx, tunnelID, port, protocol, *allowAnon) 创建端口
ListPorts(ctx, tunnelID) 查询端口列表
ShowPort(ctx, tunnelID, port) 查询端口详情
UpdatePort(ctx, tunnelID, port, *allowAnon) 更新端口
DeletePort(ctx, tunnelID, port) 删除端口
Host / Connect
方法 说明
Host(ctx, HostConfig) 托管本地端口(阻塞)
Connect(ctx, ConnectConfig) 连接远端隧道(阻塞)

架构

设备 A (Host)                          设备 B (Connect)
┌─────────────┐                       ┌──────────────┐
│ 本地服务     │                       │ 访问者       │
│ :8080       │                       │ localhost:8080│
└──────┬──────┘                       └──────┬───────┘
       │                                     │
  ┌────┴────┐                          ┌─────┴─────┐
  │ Host    │                          │ Connect   │
  │ (SDK)   │                          │ (SDK)     │
  └────┬────┘                          └─────┬─────┘
       │ WebSocket 出站                      │ WebSocket 出站
       └───────────┐     ┌──────────────────┘
                   ▼     ▼
              ┌─────────────────┐
              │ DevBridge 中继   │
              │ (SSH over WS)   │
              └─────────────────┘

Host 和 Connect 都主动连接 DevBridge 中继,因此 Host 所在设备不需要开放公网入站端口。

错误处理

err := client.Host(ctx, cfg)
if err != nil {
    switch {
    case errors.Is(err, devbridge.ErrTunnelNotFound):
        // 隧道不存在
    case errors.Is(err, devbridge.ErrDuplicateHost):
        // 已有 Host 在运行
    case errors.Is(err, devbridge.ErrQuotaExceeded):
        // 配额超限
    default:
        // 其他错误
    }

    // 检查 API 业务错误
    if code, ok := devbridge.IsAPIError(err); ok {
        fmt.Println("错误码:", code) // 如 "HD.98320078"
    }
}

依赖

  • github.com/coder/websocket — WebSocket 客户端
  • github.com/microsoft/dev-tunnels-ssh — SSH 隧道协议

Documentation

Index

Examples

Constants

View Source
const (
	// DefaultAPIBaseURL REST API 基础地址(与 CLI 生产环境对齐)
	// 对应 cli/.goreleaser.yaml 中注入的 DefaultServerDomain + cli/internal/api/client.go 的路径后缀
	DefaultAPIBaseURL = "https://bridge.developer.myhuaweicloud.com/open-api-inner/v1/relay-controller"

	// DefaultGatewayAddr WebSocket 网关地址
	DefaultGatewayAddr = "gateway.cn-north-4-bridge.myhuaweicloud.com:443"

	// DefaultGatewayHost WebSocket 网关 SNI host
	DefaultGatewayHost = "cn-north-4-bridge.myhuaweicloud.com"

	// DefaultClusterID 默认集群
	DefaultClusterID = "cn-north-4-bridge"
)

默认服务地址

View Source
const AllPortsSentinel = -1

AllPortsSentinel 表示"所有端口"的哨兵值,对应后端存储的 -1。 它只对 visitor URL 访问有意义(网关按 SNI 动态路由任意端口), host/connect 的 SSH 主动端口转发不应转发该值。

Variables

View Source
var ErrDuplicateHost = errors.New("host already connected for this tunnel")

ErrDuplicateHost 该隧道已有 Host 在运行

View Source
var ErrInvalidPort = errors.New("invalid port number: must be 1-65535")

ErrInvalidPort 端口号无效

View Source
var ErrInvalidProtocol = errors.New("invalid protocol: must be http, https, or auto")

ErrInvalidProtocol 协议无效

View Source
var ErrInvalidScope = errors.New("invalid token scope: must be host or connect")

ErrInvalidScope 令牌 scope 无效

View Source
var ErrInvalidTunnelDescription = errors.New("invalid tunnel description: only Chinese characters, letters, digits, length 0-64")

ErrInvalidTunnelDescription 隧道描述无效

View Source
var ErrInvalidTunnelID = errors.New("invalid tunnel ID: must be 8 chars of lowercase letters and digits 2-7")

ErrInvalidTunnelID 隧道 ID 格式无效

View Source
var ErrMissingAPIKey = errors.New("missing API key: set it via Config.APIKey or HW_API_KEY env var")

ErrMissingAPIKey 缺少 API Key

View Source
var ErrQuotaExceeded = errors.New("account quota exceeded")

ErrQuotaExceeded 配额超限

View Source
var ErrTunnelNotFound = errors.New("tunnel not found")

ErrTunnelNotFound 隧道不存在

Functions

func IsAPIError

func IsAPIError(err error) (code string, ok bool)

IsAPIError 判断 error 是否为 APIError,并返回错误码

Types

type APIError

type APIError struct {
	Code    string // 错误码,如 "HD.98320078"
	Message string // 错误描述
}

APIError 表示服务端返回的业务错误

func (*APIError) Error

func (e *APIError) Error() string

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client DevBridge SDK 客户端

Example (Connect)
client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
if err != nil {
	log.Fatal(err)
}

// 连接隧道,在本地建立端口映射
// 连接成功后,http://localhost:8080 → 远端 Host 的 8080 端口
err = client.Connect(context.Background(), devbridge.ConnectConfig{
	TunnelID: "aaaadysa",
	Ports:    []int{8080},
})
if err != nil {
	log.Fatal(err)
}
Example (FullWorkflow)
ctx := context.Background()

// 创建客户端(API Key 也可通过 HW_API_KEY 环境变量设置)
client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
if err != nil {
	log.Fatal(err)
}

// 1. 创建隧道
tunnel, err := client.CreateTunnel(ctx, "my-dev-tunnel", "开发联调环境", nil)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("隧道已创建: %s\n", tunnel.ID)

// 2. 添加端口
allowAnon := true
if err := client.CreatePort(ctx, tunnel.ID, 8080, "http", &allowAnon); err != nil {
	log.Fatal(err)
}

// 3. Host 托管(在服务所在设备运行)
//
// 这会阻塞,通常在单独的 goroutine 中运行:
hostCtx, hostCancel := context.WithCancel(context.Background())
go func() {
	if err := client.Host(hostCtx, devbridge.HostConfig{
		TunnelID: tunnel.ID,
		Ports:    []int{8080},
	}); err != nil {
		log.Printf("Host 退出: %v", err)
	}
}()

time.Sleep(2 * time.Second) // 等待 Host 就绪

// 4. Connect 连接(在访问设备运行)
//
// 连接成功后,在访问设备上 http://localhost:8080 即可访问远端服务
connectCtx, connectCancel := context.WithCancel(context.Background())
go func() {
	if err := client.Connect(connectCtx, devbridge.ConnectConfig{
		TunnelID: tunnel.ID,
		Ports:    []int{8080},
	}); err != nil {
		log.Printf("Connect 退出: %v", err)
	}
}()

time.Sleep(5 * time.Second)

// 5. 清理
connectCancel()
hostCancel()
client.DeleteTunnel(ctx, tunnel.ID)
Example (Host)
client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
if err != nil {
	log.Fatal(err)
}

// 查询隧道端口
ports, err := client.ListPorts(context.Background(), "aaaadysa")
if err != nil {
	log.Fatal(err)
}

portList := make([]int, len(ports))
for i, p := range ports {
	portList[i] = int(p.Port)
}

// 启动 Host(阻塞)
err = client.Host(context.Background(), devbridge.HostConfig{
	TunnelID: "aaaadysa",
	Ports:    portList,
})
if err != nil {
	log.Fatal(err)
}
Example (HostWithToken)
client, err := devbridge.NewClient(devbridge.Config{})
if err != nil {
	log.Fatal(err)
}

// 先签发 Host 令牌
token, err := client.IssueToken(context.Background(), "aaaadysa", "host")
if err != nil {
	log.Fatal(err)
}

// 用令牌启动 Host,不再调用 REST API
err = client.Host(context.Background(), devbridge.HostConfig{
	TunnelID: "aaaadysa",
	JWTToken: token.Token,
})
if err != nil {
	log.Fatal(err)
}
Example (TunnelManagement)
ctx := context.Background()
client, err := devbridge.NewClient(devbridge.Config{APIKey: "your-api-key"})
if err != nil {
	log.Fatal(err)
}

// 创建隧道,有效期 24 小时
exp := 24
tunnel, _ := client.CreateTunnel(ctx, "my-tunnel", "描述", &exp)

// 查询隧道列表
tunnels, _ := client.ListTunnels(ctx)
for _, t := range tunnels {
	fmt.Printf("%s: %s\n", t.ID, t.Name)
}

// 查询隧道详情
detail, _ := client.ShowTunnel(ctx, tunnel.ID)
fmt.Printf("端口数: %d\n", detail.Status.HostConnectionCount)

// 更新隧道
newName := "renamed-tunnel"
client.UpdateTunnel(ctx, tunnel.ID, &newName, nil, nil)

// 删除隧道
client.DeleteTunnel(ctx, tunnel.ID)

func NewClient

func NewClient(cfg Config) (*Client, error)

NewClient creates a new SDK client from the given Config. A zero Config is valid; APIKey falls back to HW_API_KEY env var, and other fields fall back to sensible defaults.

最少只需要 API Key:

client, err := devbridge.NewClient(devbridge.Config{
    APIKey: "your-api-key",
})

也会自动读取 HW_API_KEY 环境变量:

os.Setenv("HW_API_KEY", "your-key")
client, err := devbridge.NewClient(devbridge.Config{})

func (*Client) Connect

func (c *Client) Connect(ctx context.Context, cfg ConnectConfig) error

Connect 启动 Connect 连接服务

这是一个阻塞方法,在 ctx 被取消或连接彻底断开时返回。 网络短暂中断会自动重连。

基本用法:

err := client.Connect(ctx, devbridge.ConnectConfig{
    TunnelID: "aaaadysa",
    Ports:    []int{8080},
})

使用 API Key 鉴权:

err := client.Connect(ctx, devbridge.ConnectConfig{
    TunnelID: "aaaadysa",
    APIKey:   "your-api-key",
})

使用已有 JWT 令牌:

err := client.Connect(ctx, devbridge.ConnectConfig{
    TunnelID: "aaaadysa",
    JWTToken: "your-jwt-token",
})

func (*Client) CreatePort

func (c *Client) CreatePort(ctx context.Context, tunnelID string, port int, protocol string, allowAnonymous *bool) error

CreatePort 在隧道上创建端口

allowAnon := true
client.CreatePort(ctx, "tunnelId", 8080, "http", &allowAnon)

func (*Client) CreateTunnel

func (c *Client) CreateTunnel(ctx context.Context, name, description string, expiration *int) (*Tunnel, error)

CreateTunnel 创建隧道

client.CreateTunnel(ctx, "my-tunnel", "描述", nil)  // 默认有效期 72h
exp := 24
client.CreateTunnel(ctx, "my-tunnel", "", &exp)     // 24h

func (*Client) DeleteAllTunnels

func (c *Client) DeleteAllTunnels(ctx context.Context) error

DeleteAllTunnels 删除当前工作空间的全部隧道

⚠️ 谨慎调用,会删除所有隧道

func (*Client) DeletePort

func (c *Client) DeletePort(ctx context.Context, tunnelID string, port int) error

DeletePort 删除端口

func (*Client) DeleteTunnel

func (c *Client) DeleteTunnel(ctx context.Context, tunnelID string) error

DeleteTunnel 删除指定隧道

func (*Client) GetLimits

func (c *Client) GetLimits(ctx context.Context) (*Limits, error)

GetLimits 查询当前配额

func (*Client) Host

func (c *Client) Host(ctx context.Context, cfg HostConfig) error

Host 启动 Host 托管服务

这是一个阻塞方法,在 ctx 被取消或连接彻底断开时返回。 网络短暂中断会自动重连。

基本用法(已有隧道和端口配置):

err := client.Host(ctx, devbridge.HostConfig{
    TunnelID: "aaaadysa",
    Ports:    []int{8080},
})

使用 API Key 鉴权(跳过令牌签发):

err := client.Host(ctx, devbridge.HostConfig{
    TunnelID: "aaaadysa",
    APIKey:   "your-api-key",
})

使用已有 JWT 令牌(跳过 API 调用):

err := client.Host(ctx, devbridge.HostConfig{
    TunnelID: "aaaadysa",
    JWTToken: "your-jwt-token",
})

func (*Client) IssueToken

func (c *Client) IssueToken(ctx context.Context, tunnelID, scope string) (*TunnelToken, error)

IssueToken 签发隧道令牌

scope 必须是 "host" 或 "connect":

token, _ := client.IssueToken(ctx, "tunnelId", "host")    // Host 令牌
token, _ := client.IssueToken(ctx, "tunnelId", "connect") // Connect 令牌

func (*Client) ListPorts

func (c *Client) ListPorts(ctx context.Context, tunnelID string) ([]Port, error)

ListPorts 查询隧道的端口列表

func (*Client) ListTunnels

func (c *Client) ListTunnels(ctx context.Context) ([]Tunnel, error)

ListTunnels 查询当前工作空间的有效隧道列表

func (*Client) ShowPort

func (c *Client) ShowPort(ctx context.Context, tunnelID string, port int) (*Port, error)

ShowPort 查询端口详情

func (*Client) ShowTunnel

func (c *Client) ShowTunnel(ctx context.Context, tunnelID string) (*TunnelDetail, error)

ShowTunnel 查询隧道详情

func (*Client) UpdatePort

func (c *Client) UpdatePort(ctx context.Context, tunnelID string, port int, allowAnonymous *bool) error

UpdatePort 更新端口的匿名访问策略

allowAnon := false
client.UpdatePort(ctx, "tunnelId", 8080, &allowAnon)  // 禁止匿名访问
client.UpdatePort(ctx, "tunnelId", 8080, nil)          // 不修改

func (*Client) UpdateTunnel

func (c *Client) UpdateTunnel(ctx context.Context, tunnelID string, name, description *string, expiration *int) error

UpdateTunnel 更新隧道

只传需要修改的字段,nil 表示不修改:

name := "new-name"
desc := "new-desc"
client.UpdateTunnel(ctx, "tunnelId", &name, &desc, nil)  // 改名称和描述,不改有效期

type Config

type Config struct {
	// APIKey authenticates REST API requests (X-API-Key header).
	// Defaults to HW_API_KEY environment variable.
	APIKey string

	// APIBaseURL is the REST API base URL.
	// Defaults to DefaultAPIBaseURL.
	APIBaseURL string

	// GatewayAddr is the WebSocket gateway address (host:port).
	// Defaults to DefaultGatewayAddr.
	GatewayAddr string

	// GatewayHost is the WebSocket gateway SNI host.
	// Defaults to DefaultGatewayHost.
	GatewayHost string

	// HTTPClient optionally overrides the HTTP client.
	// If nil, a default client with 30s timeout is used.
	HTTPClient *http.Client

	// StatusWriter receives user-facing status lines (connection progress,
	// hosted ports, forwarding info). Defaults to os.Stdout; set it to
	// io.Discard to silence these outputs.
	StatusWriter io.Writer
}

Config holds the SDK client configuration. A zero Config is valid — missing fields fall back to environment variables and sensible defaults.

type ConnectConfig

type ConnectConfig struct {
	TunnelID string // 隧道 ID
	Ports    []int  // 端口列表(为空时从 Host 端通过 SSH 下发)
	JWTToken string // JWT 令牌(与 APIKey 二选一)
	APIKey   string // API Key(与 JWTToken 二选一)
	LocalIP  string // 本地监听地址,默认 127.0.0.1

	// OnReady 在会话就绪(本地端口映射建立)后调用,参数为本次建立的映射列表。
	// 每次连接或重连成功都会触发一次。可为 nil。
	OnReady func(forwardings []Forwarding)
}

ConnectConfig Connect 连接配置

type Forwarding

type Forwarding struct {
	LocalPort  int    // 本地端口
	RemotePort int    // 远端端口
	LocalIP    string // 本地监听地址
}

Forwarding 端口转发映射信息

type HostConfig

type HostConfig struct {
	TunnelID string // 隧道 ID
	Ports    []int  // 本地端口列表(为空时从网关下发)
	JWTToken string // JWT 令牌(与 APIKey 二选一)
	APIKey   string // API Key(与 JWTToken 二选一)

	// OnReady 在会话就绪(端口开始转发)后调用,参数为本次托管的端口列表。
	// 每次连接或重连成功都会触发一次。可为 nil。
	OnReady func(ports []int)
}

HostConfig Host 托管配置

type HostResult

type HostResult struct {
	TunnelID  string // 隧道 ID
	Ports     []int  // 实际托管的端口
	TunnelURL string // 隧道访问地址
}

HostResult Host 运行结果信息

type Limits

type Limits struct {
	ResetAt                          int64 `json:"resetAt"`
	QuotaBytes                       int64 `json:"quotaBytes"`
	RemainingBytes                   int64 `json:"remainingBytes"`
	ActiveTunnels                    int64 `json:"activeTunnels"`
	MaxTunnels                       int32 `json:"maxTunnels"`
	MaxPortsPerTunnel                int32 `json:"maxPortsPerTunnel"`
	MaxHostsPerTunnel                int32 `json:"maxHostsPerTunnel"`
	MaxTunnelBandwidthBytesPerSecond int64 `json:"maxTunnelBandwidthBytesPerSecond"`
	MaxHTTPRequestsPerMinutePerPort  int32 `json:"maxHttpRequestsPerMinutePerPort"`
	MaxConnectionsPerPort            int32 `json:"maxConnectionsPerPort"`
}

Limits 配额

type Port

type Port struct {
	TunnelID       string `json:"tunnelId"`
	Port           int32  `json:"port"`
	Protocol       string `json:"protocol"`       // http, https, auto
	AllowAnonymous bool   `json:"allowAnonymous"` // 是否允许匿名访问
}

Port 端口配置

type Tunnel

type Tunnel struct {
	ID               string `json:"tunnelId"`         // 隧道 ID,8 位小写 Base32
	Name             string `json:"name"`             // 隧道名称
	Description      string `json:"description"`      // 隧道描述
	ExpirationHours  int    `json:"expirationHours"`  // 有效期(小时)
	TunnelExpiration uint32 `json:"tunnelExpiration"` // 隧道过期时间(Unix 秒)
	PortCount        int    `json:"portCount"`        // 端口数量
}

Tunnel 隧道

type TunnelDetail

type TunnelDetail struct {
	Name             string        `json:"name"`
	ID               string        `json:"tunnelId"`
	TunnelExpiration uint32        `json:"tunnelExpiration"` // Unix 秒
	Description      string        `json:"description"`
	Status           *TunnelStatus `json:"status,omitempty"`
}

TunnelDetail 隧道详情(含状态)

type TunnelStatus

type TunnelStatus struct {
	ClientConnectionCount int   `json:"clientConnectionCount"` // Connect 连接数
	HostConnectionCount   int   `json:"hostConnectionCount"`   // Host 连接数
	TotalUploadBytes      int64 `json:"totalUploadBytes"`      // 上行字节
	TotalDownloadBytes    int64 `json:"totalDownloadBytes"`    // 下行字节
}

TunnelStatus 隧道运行状态

type TunnelToken

type TunnelToken struct {
	TunnelID string `json:"tunnelId"`
	Scope    string `json:"scope"` // host 或 connect
	Token    string `json:"token"` // JWT 令牌
}

TunnelToken 隧道令牌

Directories

Path Synopsis
cmd
connect-service command
Command connect-service 是服务 B:建立远程连接并验证访问服务 A 的本地服务。
Command connect-service 是服务 B:建立远程连接并验证访问服务 A 的本地服务。
host-service command
Command host-service 是服务 A:启动本地 HTTP 服务并创建托管。
Command host-service 是服务 A:启动本地 HTTP 服务并创建托管。
verify command
Command verify 用 SDK 托管本地服务,打印隧道公网 URL 供浏览器访问。
Command verify 用 SDK 托管本地服务,打印隧道公网 URL 供浏览器访问。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL