跳过导航,直接进入主要内容跳过导航,直接进入主要内容跳过导航,直接进入主要内容

ONVIF 服务端

server/ 是一个可嵌入也可独立运行的 ONVIF 服务端实现:带 Device、 Media、PTZ、Imaging 服务的虚拟摄像头模拟器。v2 传输层(server/soap) 面向真实设备嵌入重构——请求上下文、按动作鉴权、字节级可预测的 XML 输出。

安装

go get github.com/mickeyzzc/onvif-go/v2@v2.2.0

传输层架构

POST /onvif/device_service
        │
        ▼
soap.Handler.ServeHTTP
        │  1. 提取动作(local name,规范 化:GetStreamURI → GetStreamUri)
        │  2. 解码信封(WS-Security 头 + 原始 inner-XML 请求体)
        │  3. requiresAuth(action)? → authenticate(header)
        │  4. 分发:ContextHandler(RequestContext, body []byte)
        ▼
响应写出器(golden 测试锁定的字节布局,RawXML 直通)

请求体以原始 inner XML 字节到达 handler——用 soap.ParseRequest (或直接 encoding/xml)解码成具体请求类型。v1 传输层交给 handler 的 值是 encoding/xml 永远填不进去的空值,请求参数(如 ProfileToken) 静默丢失;v2 修好了这条管道。

请求上下文

以 RegisterContextHandler 注册的 handler 能感知请求来源——多网卡 地址通告的前提:

handler.RegisterContextHandler("GetStreamUri", func(rc *soap.RequestContext, body []byte) (interface{}, error) {
    ip := rc.RemoteIP              // 客户端 IP(RemoteAddr 的 host 部分)
    ctx := rc.Context()            // 请求 context:取消、超时
    _ = rc.Request                 // 底层 *http.Request
    ...
})

rc.Action 携带 WSDL 规范动作名。旧签名 RegisterHandler(action, func(body []byte) ...) 作为薄包装继续可用。

XAddr 回显

真实摄像头对每个客户端回答从该客户端网络可达的 URL。模拟器默认 采取同样行为:

  • Config.AdvertiseHost 为空(默认):把请求方源 IP 回显为所有通告 URL 的 host——GetCapabilities/GetServices 的 XAddr、流/快照 URI。
  • Config.AdvertiseHost 非空:该 host 处处生效(固定 DNS 名、反向 代理等场景)。

按动作鉴权(#16)

server.DefaultConfig() 的凭据默认为空 —— 库的默认值绝不能内置可猜 测的账号对。未配置凭据时全部动作开放(适合本地模拟器);把服务端暴露到 真实网络前,必须显式设置 Username/Password:

cfg := server.DefaultConfig()
cfg.Username = "admin"      // 自行决定
cfg.Password = "..."        // 来自宿主配置,绝不硬编码提交

配置了凭据时,默认策略只对写类动作要求认证——Set*、Remove*、 Create*、Go*,外加 SystemReboot 和 Config.AuthProtectedActions 里列出的名字。读操作对无凭据的发现客户端保持开放。未配置凭据时全部 开放(行为不变)。

WS-Security UsernameToken 两种口令形式都接受:

形式 校验
PasswordDigest(默认) Base64(SHA1(nonce + created + password)),常量时间比较
PasswordText 明文比较;AuthPolicy{AllowPasswordText: false} 可关闭
handler := soap.NewHandlerWithOptions(soap.HandlerOptions{
    Username: "admin",
    Password: "secret",
    Auth: &soap.AuthPolicy{          // nil → DefaultAuthPolicy()
        Prefixes: []string{"Set", "Remove", "Create", "Go"},
        Actions: []string{"SystemReboot"},
        All:     false,              // true = 严格模式(v1 行为)
    },
})

字节级 XML 输出(#18)

响应写出器手工拼装信封;字节布局由 golden 测试锁定,做字节级匹配的 消费方因此保持稳定。

规范大小写。 响应元素使用 ONVIF WSDL 拼写—— GetStreamUriResponse/GetSnapshotUriResponse(不是 ...URI...), 内层 MediaUri/Uri。进入的旧拼写(GetStreamURI)仍分发到规范 handler。

默认形式(与历史线上格式一致):

<?xml version="1.0" encoding="UTF-8"?>
<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope">
  <Body>
    <GetStreamUriResponse xmlns="http://www.onvif.org/ver10/media/wsdl">
      <MediaUri>
        <Uri>rtsp://198.51.100.7:8554/stream0</Uri>

显式前缀(Config.ExplicitPrefixes 或 HandlerOptions.ExplicitPrefixes):

<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Body>
    <trt:GetStreamUriResponse xmlns:trt="http://www.onvif.org/ver10/media/wsdl">
      <trt:MediaUri>
        <trt:Uri>rtsp://198.51.100.7:8554/stream0</trt:Uri>

前缀表:tds: trt: tptz: timg: tev: tt: trc: tan:。 无约定前缀的命名空间保持默认 xmlns 声明形式。

Raw 通道。 任意 handler 返回 soap.RawXML 即可把预构建字节原样 嵌入——完全绕过 encoding/xml,精确控制元素名、前缀、格式。Raw 输出 不会被前缀模式改写。

可插拔 provider(M3)

SOAP 层背后的每个状态来源都是 server/provider 里的接口——模拟器 (server/simulator)只是默认实现:

Provider 支撑 用于替换
DeviceInfoProvider GetDeviceInformation 硬件标识 / EEPROM
StreamURIProvider(可选 StreamURISetter) GetStreamUri、RTSP 路径 真实 RTSP 挂载点
SnapshotProvider HTTP /snapshot 端点 JPEG 帧缓冲
ImagingProvider Get/SetImagingSettings、GetOptions、Move ISP / 镜头控制
PTZProvider 移动、状态、预置位 电机驱动(或继续用模拟器)
srv, err := server.New(config,
    server.WithDeviceInfoProvider(hwInfo),
    server.WithStreamURIProvider(rtspMounts),
    server.WithSnapshotProvider(frameBuffer),
    server.WithImagingProvider(isp),
)

不传选项 = 完整模拟器行为(CLI 与示例零改动)。SOAP handler 现在是 无状态的翻译层;Server.Handle* 签名、导出的模型类型、领域错误哨兵 保持 server.* 拼写(别名兼容)。

预置位存储(v2.2.0): PTZProvider 继续负责移动/状态/GotoPreset; 可选的 PTZPresetReader 让 GetPresets 超越静态配置,PTZPresetWriter 解锁 SetPreset / RemovePreset——模拟器两者都实现了,带每 profile 可变 存储(set → list → goto → remove 开箱即用)。GetConfigurations 与 GetNodes 按档案配置以 WSDL 正确命名空间应答。

TLS 传输(Profile T 基线)

v2.1.0 起设置 Config.TLSCertFile 与 Config.TLSKeyFile(两者同设或 同不设——只设一个会在校验时报错)后,服务端走 HTTPS。没有单独的启动 调用:TLS 在 Start 内部决定。Start 显式绑定监听,Server.ListenAddr() 启动即可读——Port: 0(内核分配)时也可观察,适合测试:

config := server.DefaultConfig()
config.TLSCertFile = "cert.pem"
config.TLSKeyFile = "key.pem"

srv, _ := server.New(config)
go srv.Start(ctx)                           // https://…/onvif/device_service
fmt.Println(srv.ListenAddr())               // 如 0.0.0.0:8443

范围:纯 TLS 传输(Profile T 基线)。高级置备——PKI、802.1X、 UsernameToken 之外的 WS-Security——刻意不做。

可运行的 onvif-server 二进制只说纯 HTTP;TLS 是嵌入包的面 (onvif-server CLI)。

去向

可插拔 provider(上文)直接前接真实相机栈;pull-point 事件服务 (SupportEvents + PublishEvent 接缝)见事件手册。分层见 v2-architecture.md。

可访问性增强功能已加载,支持焦点管理、键盘导航和屏幕阅读器
页面已加载完成,可访问性功能已启用