Saxon-HE 10.9.0 中 unparsed-text() 读取 HTTPS 失败如何排查?
结论
应先检查 Saxon/IKVM 的 URL 处理和运行时依赖,再根据底层异常判断是否涉及 TLS。现有错误信息不足以证明 TLS 协议版本是原因,添加 ikvm:https.protocols 也不一定能解决问题。
如果当前目标是恢复 HTML 生成流程,可以先由 .NET 下载 JavaScript,再通过 XSLT 参数传入脚本正文。这样既能生成内联脚本,也能绕过 unparsed-text() 对远程 HTTPS 资源的访问。
为什么 XMLSpy 能运行,.NET 中却失败?
XMLSpy 中运行成功,说明该样式表在 XMLSpy 的执行环境中能够读取资源,不能说明 .NET 程序使用了相同的处理器、网络配置或运行时依赖。
Saxon 10 默认的 StandardUnparsedTextResolver 使用 Java URL 的处理能力读取外部文本。在题述的 .NET/IKVM 环境中,可以优先检查这条调用路径。Saxon 10 的解析器文档
示例使用的是完整的绝对 HTTPS URI,因此不必优先修改样式表的相对路径或 Base URI。异常在 ApplyTemplates(...) 处向应用程序抛出,但仅凭这一行无法确定真正的故障点。
先取得底层异常,再检查配置
建议按以下顺序排查:
- 记录完整异常,包括堆栈和内部异常。检查是否存在协议处理器加载失败、程序集加载失败、证书错误或 TLS 握手错误。
- 核对实际加载的 Saxon 和 IKVM 程序集版本及路径,检查输出目录是否混入了其他版本的依赖。
- 在运行应用的同一台机器上,使用同一账户,通过 .NET HTTP 客户端请求相同地址。若请求成功,可以进一步将排查范围缩小到 Saxon/IKVM 的访问路径;若请求失败,则继续检查该请求报告的网络、代理或证书错误。
- 只有底层异常指向 TLS,并且已确认当前 IKVM 版本支持相应配置时,再调整协议设置。
原配置中的 connectionStrings 部分应删除。这个配置节用于数据库等连接字符串,不能用 appSettings 的 key/value 写法配置 IKVM。
还需确认当前 IKVM 实现是否识别 appSettings 中的设置,以及宿主程序实际加载了哪份配置文件。对于传统 .NET Framework 可执行程序,应检查部署后的 应用名.exe.config;宿主类型不同时,配置加载方式也可能不同。
替代方案:下载脚本后,通过参数传入 XSLT
如果项目允许调整样式表和调用代码,可以采用这个方案。下载成功后,XSLT 只接收字符串,不再调用远程 unparsed-text()。
例如,将下面的样式表保存为 sheet.xsl:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:param name="jquery-source" as="xs:string" required="yes"/>
<xsl:template name="xsl:initial-template">
<html>
<head>
<title>Example</title>
<script>
<xsl:value-of select="$jquery-source"/>
</script>
</head>
<body/>
</html>
</xsl:template>
</xsl:stylesheet>
对应的 C# 方法如下,调用方使用 await GenerateAsync() 执行:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Saxon.Api;
public static class HtmlGenerator
{
public static async Task GenerateAsync()
{
string javascript;
using (var http = new HttpClient())
{
javascript = await http.GetStringAsync(
"https://code.jquery.com/jquery-3.5.1.min.js");
}
var processor = new Processor();
var compiler = processor.NewXsltCompiler();
var executable = compiler.Compile(
new Uri(Path.GetFullPath("sheet.xsl")));
var transformer = executable.Load30();
transformer.SetStylesheetParameters(
new Dictionary<QName, XdmValue>
{
{
new QName("jquery-source"),
new XdmAtomicValue(javascript)
}
});
using (var output = File.Create("output.html"))
{
var serializer = processor.NewSerializer(output);
transformer.CallTemplate(null, serializer);
}
}
}
SetStylesheetParameters(...) 用于设置全局样式表参数,必须在执行转换之前调用;CallTemplate(null, ...) 调用 xsl:initial-template。Saxon 10 的 Xslt30Transformer 文档中有这些接口的说明。
这个示例不需要输入 XML。如果原有转换依赖输入文档,可以保留原来的 ApplyTemplates(...) 调用,只在执行前设置参数,并将读取脚本的表达式改为 $jquery-source。
如果必须保留 unparsed-text()
可以先把脚本保存为本地文件,再让样式表读取本地 URI。这样可以单独验证文件读取路径,也不必在每次转换时访问远程服务器:
<xsl:param name="jquery-uri" as="xs:string" required="yes"/>
<xsl:value-of select="unparsed-text($jquery-uri, 'UTF-8')"/>
这里应传入脚本文件的绝对 file: URI;示例中的编码以文件实际保存为 UTF-8 为前提。unparsed-text() 的第二个参数用于指定字符编码,不控制 TLS 或 URL 协议处理。Saxon 10 的函数文档
如果准备自定义解析器,要区分普通 XML 文档解析与文本资源解析。Saxon 10 为 unparsed-text() 提供的是 UnparsedTextURIResolver 配置,不能假定修改普通 InputXmlResolver 就会影响它。在 .NET 中接入该接口时,还要核对实际使用的交叉编译程序集和 API。Saxon 10 的解析器配置说明
如果最终确认问题出在当前 IKVM 依赖上,再评估兼容的运行时或打包方案。在取得完整底层异常之前,无法承诺某个版本升级就能修复问题。
备注:内容仅供参考。
