当 responseText 包含有效 Xml 时,IXMLHttpRequest.responseXml 为空,没有解析错误

Posted

技术标签:

【中文标题】当 responseText 包含有效 Xml 时,IXMLHttpRequest.responseXml 为空,没有解析错误【英文标题】:IXMLHttpRequest.responseXml is empty, with no parse error, when responseText contains valid Xml 【发布时间】:2012-04-14 07:08:45 【问题描述】:

我正在从 a government web-site 获取一些 XML:

http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_all.xml

我正在使用以下相当简单的代码:

var
   szUrl: string;
   http: IXMLHTTPRequest;
begin
   szUrl := 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_all.xml';

   http := CoXMLHTTP60.Create;
   http.open('GET', szUrl, False, '', '');
   http.send(EmptyParam);

   Assert(http.Status = 200);

   Memo1.Lines.Add('HTTP/1.1 '+IntToStr(http.status)+' '+http.statusText);
   Memo1.Lines.Add(http.getAllResponseHeaders);
   Memo1.Lines.Add(http.responseText);

我不会显示所有返回的正文,但它确实会在responseText 中返回有效的 xml:

HTTP/1.1 200 OK
Cache-Control: max-age=5
Connection: keep-alive
Connection: Transfer-Encoding
Date: Fri, 30 Mar 2012 14:50:50 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
Expires: Fri, 30 Mar 2012 14:50:55 GMT
Server: Apache/2.2.16 (Unix) php/5.3.3 mod_ssl/2.2.16 OpenSSL/1.0.0d mod_perl/2.0.4 Perl/v5.12.0
X-Powered-By: PHP/5.3.3


<?xml version="1.0" encoding="ISO-8859-1"?>
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns="http://purl.org/rss/1.0/"
    xmlns:cb="http://www.cbwiki.net/wiki/index.php/Specification_1.1"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:dcterms="http://purl.org/dc/terms/"
    xmlns:xsi="http://www.w3c.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.w3c.org/1999/02/22-rdf-syntax-ns#rdf.xsd">
    <channel rdf:about="http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_ALL.xml">
        <title xml:lang="en">Bank of Canada: Noon Foreign Exchange Rates</title>
        <link>http://www.bankofcanada.ca/rates/exchange/noon-rates-5-day/</link>

好的,好的,里面有有效的 xml。我知道它是有效的,因为......好吧,看看它。但我也知道通过解析它是有效的:

var
   ...
   szXml: WideString;
   doc: DOMDocument60;
begin
   ...
   szXml := http.responseText;
  
   doc.loadXML(szXml);
   Assert(doc.parseError.errorCode = 0);

   Memo1.Lines.Add('============parsed xml');
   Memo1.Lines.Add(doc.xml);

原始的IXmlHttpRequest 包含一个responseXml 属性。来自 MSDN:

表示解析后的响应实体主体。

如果响应实体正文不是有效的 XML,则此属性返回已解析的 DOMDocument,以便您可以访问错误。此属性本身不返回 IXMLDOMParseError,但可从 DOMDocument 访问。

在我的情况下 responseXml 属性存在,因为它应该:

Assert(http.responseXml <> nil);

并且没有responseText的解析错误:

doc := http.responseXml as DOMDocument60;
Assert(doc.parseError.errorCode = 0);

应该有,因为 xml 是有效的。

除了当我查看http.responseXml 文档对象时,它是空的:

   Memo1.Lines.Add('============responseXml');
   Memo1.Lines.Add(doc.xml);

是 IXMLHttpRequest(和 IXMLServerHttpRequest)返回一个空的 XML 文档,当:

有xml xml 有效 没有解析错误

长格式:

uses
    msxml2_tlb;

procedure TForm1.Button1Click(Sender: TObject);
var
    szUrl: string;
    http: IXMLHTTPRequest;
    doc: DOMDocument60;
begin
    szUrl := 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_all.xml';

    http := CoXMLHTTP60.Create; //or CoServerXmlHttpRequest.Create
    http.open('GET', szUrl, False, '', '');
    http.send(EmptyParam);

    Assert(http.Status = 200);

    doc := http.responseXml as DOMDocument60;
    Assert(doc.parseError.errorCode = 0);

    ShowMessage('"'+doc.xml+'"');
end;

我如何使XmlHttpRequest(更重要的是ServerXMLHTTP60)的行为符合记录?

【问题讨论】:

Delphi 版本信息是所有涉及 RTL 和标准库的问题的关键。什么版本? 我没有使用 Delphi 的任何组件,而是 Delphi 5。 相关:***.com/questions/8925798/… - 注意 Keepalive 评论。试过了吗?另外,什么 IE 版本和 MS XML 版本,因为这些在这些情况下也很重要。我相信 MS XML 的 HTTP 方法使用 WinInet,它有一些有趣的错误,并且当你更新 IE 时它会更新。 @WarrenP 我尝试了超时;它不会改变结果(也不应该,因为我得到了有效的响应)。 ie9,msxml 6.0。如果您复制粘贴最终的简化 8 行版本,您会得到相同的行为吗? 我认为这与您使用 DOMDocument60 而不是 http.responseText 有关。 【参考方案1】:

我发现了问题

我使用Fiddler 将http 响应保存到文本文件。之后我可以修改响应文件,并指示提琴手提供我手工制作的替代品,而不是去原始网站。

经过 3 个小时的摆弄,我设法在原始 http 响应标头中找到了问题:

HTTP/1.1 200 OK
Cache-Control: max-age=5
Connection: keep-alive
Connection: Transfer-Encoding
Date: Fri, 30 Mar 2012 14:50:50 GMT
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
Expires: Fri, 30 Mar 2012 14:50:55 GMT
Server: Apache/2.2.16 (Unix) PHP/5.3.3 mod_ssl/2.2.16 OpenSSL/1.0.0d mod_perl/2.0.4 Perl/v5.12.0
X-Powered-By: PHP/5.3.3

应该是:

HTTP/1.1 200 OK
Cache-Control: max-age=5
Connection: keep-alive
Connection: Transfer-Encoding
Date: Fri, 30 Mar 2012 14:50:50 GMT
Transfer-Encoding: chunked
Content-Type: text/xml; charset=UTF-8
Expires: Fri, 30 Mar 2012 14:50:55 GMT
Server: Apache/2.2.16 (Unix) PHP/5.3.3 mod_ssl/2.2.16 OpenSSL/1.0.0d mod_perl/2.0.4 Perl/v5.12.0
X-Powered-By: PHP/5.3.3

一旦我发现问题,我就可以回溯the documentation that explain the behavior:

MSXML 6.0 支持的 MIME 类型有:

文本/xml” “应用程序/xml” 或以“+xml”结尾的任何内容,例如“application/rss+xml

我正在获取的 RSS 提要实际上是 Resource Definition Format (RDF) feed,其内容类型应该是:

application/rdf+xml

他们的用途:

text/html

在很多层面上都是错误的。

所以我遇到的行为是设计使然;虽然令人沮丧 - 因为没有简单的方法可以知道 responseXml 是否“有效”。

responseXml 对象将被分配 parseError 对象将被分配 parseError.ErrorCode 为零 responseXml.documentElement 将为零

【讨论】:

我的猜测是,由于不支持 text/html,底层 XML 解析器甚至不解析任何数据,这将使 errorCode 设置为零但不会创建 documentElement 对象.如果responseText 不是空的,但documentElement 是空的,那么你就知道出了问题,你可以测试一下。 该站点上的许多中午提要都将Content-Type 发送为text/html,但其中一些,例如“en_USD.xml”和“en_MXN.xml”,改为发送application/xml . 那该怎么办呢?通过一些字符串破解来破解他们的 XML? @WarrenP 我回退到if http.responseXml.documentElement = nil then result := GetXmlObjectFromXmlString(http.responseText) else result := http.responseXml; 这不是一个完全通用的解决方案,因为有时可能会有一个带有 no documentElement 的 xml 文档。但在这种情况下,这就足够了,因为如果 xml 真的是空的,那么他们做了一些愚蠢的事情。【参考方案2】:

YouTube 服务也有同样的问题。

responseXml 对象取决于响应的内容类型/MIME。 您可以检查Content-Type 的响应,例如:如果http.getResponseHeader('Content-Type') 包含text/xmlapplication/xml 只有那么您可以参考http.responseXml,否则它将为空(请参阅MSDN Remarks )。另请注意,出于安全原因,responseXml 解析器验证功能始终处于关闭状态。

但是http.responseText总是xml 文本,无论响应中的内容类型是什么,所以你可以总是 使用DOMDocument 的新实例来加载xml 例如:

...
http := CoXMLHTTP60.Create; // or CoServerXmlHttpRequest.Create 
http.open('GET', szUrl, False, '', '');
http.send(EmptyParam);
Assert(http.Status = 200);

doc := CreateOleObject('Msxml2.DOMDocument.6.0') as DOMDocument60; 
doc.async := False;
doc.loadXML(http.responseText); // <- load XmlHttpRequest.responseText into DOMDocument60 and use it
Assert(doc.parseError.errorCode = 0);

// do useful things with doc object...

【讨论】:

我不使用 DOMDocument 对象(严格来说我不使用 XmlHttpRequest 对象)因为羊头企业认为阻止人们访问互联网是个好主意(即我必须配置一个代理 - 这只能使用 IServerXmlHttpRequest 完成)。此外,您不能只检查text/xml,还需要检查application/xmlanything/anything+xml(充满了我什至不想关心的边缘情况)。 仔细阅读我的答案。我的意思是,如果响应明确设置为text/xml,您将有一个有效的responseXml.documentElement 对象。我是说不要依赖responseXml 并始终使用responseTextDOMDocument 就像在第一个代码示例中一样。当响应内容类型不是 text/xml 时,IServerXmlHttpRequest 的行为与 XmlHttpRequest 相同。 更喜欢让 XmlHttpRequest 处理响应 xml 文本的解析;否则 xml 必须经过另一个编码周期(编码为 UTF-16 BSTR,然后再次解析)。即使内容类型为text/xml,documentElement 也可能为 nil - 所以这是一个陷阱。 我也更喜欢那个。但这是设计使然。 ://【参考方案3】:

嗯,这在 Delphi XE 和 Delphi 7 中有效:

procedure TForm1.Button1Click(Sender: TObject);
var
    szUrl: string;
    http: IXMLHTTPRequest;
    doc: $ifndef UNICODEWideString$elsestring$endif;
begin
    szUrl := 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_all.xml';

    http := CoXMLHTTP60.Create; //or CoServerXmlHttpRequest.Create
    http.open('GET', szUrl, False, '', '');
    http.setRequestHeader('Content-Type', 'text/xml;charset=UTF-8');
    http.send(EmptyParam);

    Assert(http.Status = 200);

    doc := UTF8Encode(http.responseText);

    Memo1.Lines.text := doc;
//  ShowMessage('"'+doc.xml+'"');
end;

希望它也适用于 Delphi 5。当然,任何 unicode 字符都会变成 ?在你身上,在非 unicode delphi 版本中。

【讨论】:

respoonseText 有效,responseXml 为空(尽管没有解析错误)【参考方案4】:

您正在从DOMDocument 对象本身检索xml,但您应该从文档树中的第一个节点获取它,例如:

doc := http.responseXml as DOMDocument60; 
Assert(doc.parseError.errorCode = 0); 
ShowMessage('"' + doc.DocumentElement.childNodes.Item(0).xml + '"'); 

Microsoft 自己在 DOMDocumentxml 属性的文档中的示例正好显示了这种逻辑。

【讨论】:

在这种情况下,documentElement 为零。 嗯,这就解释了为什么xml 是空的——DOMDocument 中什么都没有。 至少它是骗人的:DOMDocument 中没有任何内容,但它是有效的 XML。

以上是关于当 responseText 包含有效 Xml 时,IXMLHttpRequest.responseXml 为空,没有解析错误的主要内容,如果未能解决你的问题,请参考以下文章

ajax 技术有一事不明有高人指点,当我们用responseText接收response.write输出的值时有长度限制吗?

Ajax 服务器响应 XMLHttpRequest | AJAX 教程

Xmlhttprequest.responseText返回的是啥格式

Ajax的使用

responseText responseBody responseXml啥区别

ajax-服务器响应