*.nsmap 是「前缀 → URI」的表,本质就是 XML 的 xmlns。
ONVIF 需要十项,少一项就是 unbound prefix——
而且「缺 nsmap」和「路径写错」的报错长得完全不一样。
上一节生成了八个文件,这一节讲其中一个最容易被忽略的:.nsmap。
它不是代码、不是配置,是一个「对照表」,
而 gSOAP 的每个函数名、每个序列化动作都依赖它。
这一节和 08.08.09 是同一件事的两个平台版本,Linux 下放法不同。
一、它到底解决什么问题
XML 里 tt:Width 这个写法,tt 只是个「缩写」,
真正的名字是「http://www.onvif.org/ver10/schema + Width」。
nsmap 干的就是「记住这个缩写对应哪个 URI」这件事。
| 角色 | 给谁看 | 少了会怎样 |
|---|---|---|
| 发请求时 | 设备 | 设备不知道 tt 是谁,回 Fault |
| 收响应时 | gSOAP 解析器 | 解析失败,抛 unbound prefix |
| 生成函数名时 | soapcpp2 |
那一族函数生成不出来 |
第三行是这一节最反直觉的一点:nsmap 不只影响运行期,还影响函数名。
因为函数名是 soap_call___tds__GetProfiles,
中间那个 tds 就来自这张表——表里没有 tds,这族函数就不存在。
所以「少一项」的后果分两层:生成期少一族函数,运行期解不了那一族的响应。
二、十项都要哪几个
| 前缀 | URI 结尾 | 必须吗 |
|---|---|---|
SOAP-ENV |
/2003/05/soap-envelope |
必须,且必须第一项 |
xsd/xsi |
XML Schema | 必须 |
tds |
/ver10/device/wsdl |
必须 |
trt |
/ver10/media/wsdl |
取流就必须 |
tt |
/ver10/schema |
必须,出现频率最高 |
wsdd |
/ws/2005/04/discovery |
要发现就必须 |
wsse |
鉴权那一段 | 要鉴权就必须 |
第一行那个「必须第一项」不是格式要求,是 gSOAP 的约定:
它按顺序取第一个作为「信封的命名空间」,放错了会把业务命名空间当成信封的,直接解析崩。
第五行那个 tt 是「最该检查的一项」:
它不像 tds、trt 那样「看着像主类型」,很容易在抄表时漏掉,
而它出现的次数最多——漏了它,几乎所有响应都解不了。
第六、七行是「按需」:只做取流的话 wsdd 与 wsse 可以先不放,
但只要加一行「自动发现」,wsdd 就变成必须的。
三、两种病,两种报错
这一节最实用的一段:把「缺 nsmap」和「路径写错」分开。
| 症状 | 真实原因 | 怎么确认 |
|---|---|---|
| 解析时报 unbound prefix | 缺 nsmap 那一项 | 看报错里的前缀名 |
| 解析成功但字段全空 | XPath/路径写错 | 打印整棵树看有没有那个节点 |
| 发出去设备回 Fault | 请求里少了 xmlns | 抓包看 Header |
第一行和第二行的区别是「解析器到底能不能理解这个名字」:
前缀没定义,连名字都无法解释,一定抛异常;
前缀有定义但路径写错,解析器能找到树、只是没找到那个节点,返回 None。
所以「返回 None」和「抛异常」是两条完全不同的排查路线,
而很多人一看到「字段是空的」就去查 nsmap,方向就错了。
第三行是「发出去」那一侧:gSOAP 会用 nsmap 自动加 xmlns,表里没有的前缀它不会加,
于是请求里出现「用了 tt: 但没声明」的情况——设备会回 Sender 类的 Fault,
理由往往是「无法解析」。
四、那条最长的 URI 为什么要复制
wsse 那一项有 78 个字符:
http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-secext-1.0.xsd
它长得离谱,而且里面有几个容易打错的片段:oasis-200401(不是 2004-01)、
secext(不是 sec-ext)、1.0(不是 1)。
这几个地方任何一个打错,结果都是一样的:unbound prefix 或 Fault。
所以这一条最该「复制粘贴」,不要手打。
更麻烦的是「打错了也看不出来」:78 个字符里错一个,肉眼几乎发现不了,
而排查时你会怀疑鉴权算错了、时钟不对、密码不对——唯独不会怀疑「URI 里少了个连字符」。
所以我的建议是:把这张表当成「常量表」写进代码,只从官方文档复制,永远不手敲。
五、动手 Demo:命名空间表少一项就 unbound
Demo 先用一段真报文试「有 xmlns:tt 与没有」的区别,
再把十项表打出来,看那条最长的 URI 有多长。
重点看两种错的报错差别。
# 同一个报文,去掉 xmlns:tt 再解析一次
def parse(x, drop=""):
y = x if not drop else x.replace(
' xmlns:%s="http://www.onvif.org/ver10/schema"' % drop, "")
try:
r = ET.fromstring(y)
return r.find(".//{http://www.onvif.org/ver10/schema}Width").text
except ET.ParseError as e:
return "ParseError: %s" % e
print(parse(xml)) # 1920
print(parse(xml, "tt")) # unbound prefix
实跑输出:
--------------------------------------------------------------------
命名空间表少一项就 unbound
--------------------------------------------------------------------
**`*.nsmap` 是一张「前缀 → URI」的表,本质就是 XML 的 `xmlns`。**
**ONVIF 需要的项不多,但一个都不能少**:
前缀 URI
-----------------------------------------------------------
`SOAP_ENV` `http://www.w3.org/2003/05/soap-envelope`
`tds` `http://www.onvif.org/ver10/device/wsdl`
`trt` `http://www.onvif.org/ver10/media/wsdl`
`tptz` `http://www.onvif.org/ver20/ptz/wsdl`
`tev` `http://www.onvif.org/ver10/events/wsdl`
`tim` `http://www.onvif.org/ver20/imaging/wsdl`
`tan` `http://www.onvif.org/ver20/analytics/wsdl`
`tt` `http://www.onvif.org/ver10/schema`
`wsdd` `http://schemas.x…ws/2005/04/discovery`
`wsse` `http://docs.oasi…urity-secext-1.0.xsd`
**一共 10 项,其中最后两项不是 ONVIF 自己的**:
**`wsdd` 是设备发现,`wsse` 是鉴权。**
**两项都不是「可选项」**:**没有 `wsdd` 就发不出 Probe**,**没有 `wsse` 就填不了 Header。**
**`wsse` 那条在表里被我截短了,完整的是:**
`http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd`
**这一条特别长,抄错一个字符就是 unbound——所以它最该复制粘贴,不要手打。**
**用一段真报文试一次。**
**这是解析器看到的样子:**
带 `xmlns:tt` → 解析成功,Width=1920
去掉 `xmlns:tt` → **ParseError:unbound prefix: line 1, column 91**
**第二行那个报错是「前缀找不到」,不是「元素不存在」。**
**区别很重要**:
**元素不存在是「找到了但没内容」,前缀不存在是「连名字都无法解释」。**
**前者不报错(返回 None),后者一定抛异常。**
**所以「解析成功但字段全是空」和「解析直接失败」是两种病**,
**前一种是路径写错,后一种是 nsmap 缺项。**
两行输出的差别就是这一节的全部:
带 xmlns:tt 解析成功、拿到 1920;去掉它就抛 unbound prefix。
注意报错里那个「line 1, column 91」:它指的是「tt: 这个前缀第一次出现的位置」,
不是「缺少声明的位置」——所以列号对排查没什么帮助,要看的是前缀名。
后面那段「两种病」的总结是从这个实验里来的:
我把路径写错(比如找 Width 写成 Widht)会得到 None,不抛异常,
和这里的 ParseError 完全不同。
最后那个 78 字符的 URI 打印出来,
是为了让人直观看到「这条不能手打」——贴在屏幕上抄都容易错。
30 秒实验
- 把报文里的
tt:Width改成tt2:Width,看报错是 unbound 还是「找不到节点」。 - 在
parse()里加一句print(r.tag),看根元素的完整名字长什么样。 - 把
wsse那条 URI 故意删掉一个连字符,看它会不会被ET接受。
常见误区
- 把 nsmap 当成「可有可无的配置」。 它还决定函数名——
表里没有
tds,soap_call___tds__*这族函数就不存在。 - 看到「字段是空的」就去查 nsmap。 缺 nsmap 一定抛异常, 字段空是路径写错,两条完全不同的排查路线。
- 手打那条 78 字符的
wsseURI。 错一个字符症状完全相同,而你会去怀疑鉴权算法、时钟、 密码。
自测三问(点开对答案)
1. nsmap 少一项会有哪两层后果? —— **生成期那一族函数生成不出来**,**运行期解不了那一族的响应**(unbound prefix)。 2. 「解析成功但字段是空的」是不是 nsmap 的问题? —— **不是。缺 nsmap 一定抛 `unbound prefix` 异常**; **返回空说明前缀有定义,是路径或元素名写错了。** 3. 为什么 `SOAP-ENV` 必须放在第一项? —— **gSOAP 按顺序取第一个当信封的命名空间**,放错了会把业务命名空间当信封的,解析直接崩。下一节挑文件:八个产物里只拷五个,另外还要从 gSOAP 源码里拿三份。
- ☐ (本节完,下一节 08.01.08 提取需要的文件)