XML验证时第三方DTD间歇性抛HTTP 400错误问题求助
问题详情
验证XML时,第三方DTD服务间歇性返回HTTP 400(Bad Request)错误,错误栈如下:
打开外部DTD 'http://example.com/fake/path/MyDTD.dtd' 时发生错误:响应状态码表示请求失败: 400 (Bad Request)。
at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.PushExternalEntityOrSubset(String publicId, String systemId, Uri baseUri, String entityName)
at System.Xml.XmlTextReaderImpl.DtdParserProxy_PushExternalSubset(String systemId, String publicId)
at System.Xml.XmlTextReaderImpl.DtdParserProxy.System.Xml.IDtdParserAdapter.PushExternalSubset(String systemId, String publicId)
at System.Xml.DtdParser.ParseExternalSubset()
at System.Xml.DtdParser.ParseInDocumentDtd(Boolean saveInternalSubset)
at System.Xml.DtdParser.Parse(Boolean saveInternalSubset)
at System.Xml.DtdParser.System.Xml.IDtdParser.ParseInternalDtd(IDtdParserAdapter adapter, Boolean saveInternalSubset)
at System.Xml.XmlTextReaderImpl.ParseDtd()
at System.Xml.XmlTextReaderImpl.ParseDoctypeDecl()
at System.Xml.XmlTextReaderImpl.ParseDocumentContent()
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.XmlValidatingReaderImpl.Read()
at MyApi.Infrastructure.Serializers.MyCustomXmlSerializer.ValidateAgainstDtd(String xml, Boolean throwOnFailure)
in D:\a\1\s\src\Infrastructure\Serializers\MyCustomXmlSerializer.cs:line 198
当前使用的XML验证代码:
public bool ValidateAgainstDtd(string xml, bool throwOnFailure = false) { var messages = new List<string>(); try { var settings = new XmlReaderSettings { ValidationType = ValidationType.DTD, DtdProcessing = DtdProcessing.Parse, XmlResolver = new XmlPreloadedResolver(new XmlUrlResolver()) }; settings.ValidationEventHandler += (sender, args) => messages.Add(args.Message); using var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)); using var reader = XmlReader.Create(stream, settings); while (reader.Read()) { } //this is line 198 in the exception } catch (Exception e) { messages.Add($"DTD Validation Exception: {e.Message}{Environment.NewLine}{e.StackTrace}"); } if (messages.Count == 0) { return true; } if (throwOnFailure) { throw new CustomXmlSerializationException(messages.ToArray()); } return false; }
附加信息
- 仅特定调用间歇性失败,其他场景该调用可正常运行
- 本地NUnit集成测试无问题,仅预发环境出现间歇性DTD拉取失败
- 无其他XML验证错误,序列化模型在多调用中通用
可能的原因与解决办法
1. 第三方服务限流/请求频率限制
预发环境的请求量可能触发了第三方DTD服务的限流策略,导致间歇性400错误。
- 解决办法:
- 联系第三方服务提供商确认请求频率限制,调整调用频率
- 实现本地DTD缓存:将DTD文件下载到本地,验证时直接读取本地文件,避免频繁请求第三方服务。示例修改:
// 替换XmlResolver为本地文件读取 var settings = new XmlReaderSettings { ValidationType = ValidationType.DTD, DtdProcessing = DtdProcessing.Parse, XmlResolver = new LocalDtdResolver() }; // 自定义LocalDtdResolver示例 public class LocalDtdResolver : XmlResolver { public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) { // 根据DTD的systemId映射到本地文件路径 if (absoluteUri.AbsolutePath.EndsWith("MyDTD.dtd")) { string localPath = Path.Combine(AppContext.BaseDirectory, "DTDs", "MyDTD.dtd"); return new FileStream(localPath, FileMode.Open, FileAccess.Read); } throw new FileNotFoundException($"DTD file not found for {absoluteUri}"); } }
2. 预发环境网络波动
预发环境与第三方服务之间的网络可能存在间歇性波动,比如DNS解析延迟、连接超时或请求头不符合要求。
- 解决办法:
- 检查预发环境的网络连通性,确认能稳定访问第三方DTD地址
- 自定义
XmlUrlResolver,添加重试逻辑和自定义请求头:public class RetryXmlUrlResolver : XmlUrlResolver { private const int MaxRetries = 3; private readonly TimeSpan _retryDelay = TimeSpan.FromSeconds(1); public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) { int retryCount = 0; while (retryCount < MaxRetries) { try { // 添加自定义请求头,比如User-Agent var request = WebRequest.Create(absoluteUri); request.Headers.Add("User-Agent", "MyApi/1.0"); using var response = request.GetResponse(); return response.GetResponseStream(); } catch (WebException ex) when (((HttpWebResponse)ex.Response)?.StatusCode == HttpStatusCode.BadRequest && retryCount < MaxRetries - 1) { retryCount++; Thread.Sleep(_retryDelay); } } return base.GetEntity(absoluteUri, role, ofObjectToReturn); } } // 使用RetryXmlUrlResolver替换原XmlResolver var settings = new XmlReaderSettings { ValidationType = ValidationType.DTD, DtdProcessing = DtdProcessing.Parse, XmlResolver = new XmlPreloadedResolver(new RetryXmlUrlResolver()) };
3. XML中的DTD引用存在动态参数问题
如果XML中的DTD引用包含动态生成的参数(比如时间戳、签名),可能存在参数生成错误导致间歇性400。
- 解决办法:
- 检查失败请求的XML内容,确认DTD的systemId是否正确,无无效参数
- 对比成功和失败请求的XML差异,定位是否是DTD引用格式问题
4. 第三方服务间歇性故障
第三方服务本身可能存在不稳定情况,导致间歇性返回400。
- 解决办法:
- 监控第三方服务的状态,确认是否有服务波动
- 实现降级策略:当DTD拉取失败时,允许跳过验证(需评估业务风险),或使用备用DTD文件
内容的提问来源于stack exchange,提问作者Josh

