如何在 ActionScript 3 中将 “Null”(真实的姓氏!)传递给 SOAP Web 服务

我们有一个姓为 Null 的员工。当使用该姓氏作为搜索词时,我们的员工查找应用程序将被杀死(这种情况现在经常发生)。收到的错误(感谢 Fiddler!)是:

<soapenv:Fault>
   <faultcode>soapenv:Server.userException</faultcode>
   <faultstring>coldfusion.xml.rpc.CFCInvocationException: [coldfusion.runtime.MissingArgumentException : The SEARCHSTRING parameter to the getFacultyNames function is required but was not passed in.]</faultstring>

可爱吧?

参数类型为string

我在用:

  • WSDLSOAP
  • Flex 3.5
  • 动作脚本 3
  • ColdFusion 8

请注意,从 ColdFusion 页面将 Webservice 作为对象调用时,不会发生该错误。

答案

追踪

起初,我认为这是一个强制错误,其中null被强制为"null"并且测试"null" == null已通过。不是。 我很近,但是非常非常错误。对于那个很抱歉!

从那以后,我在 wonderfl.net 上做了很多摆弄,并在mx.rpc.xml.* 查找了代码。在XMLEncoder第 1795 XMLEncoder (在 3.5 源代码中),在setValue ,所有 XMLEncoding 都归结为

currentChild.appendChild(xmlSpecialCharsFilter(Object(value)));

本质上与以下内容相同:

currentChild.appendChild("null");

根据我的原始提琴,此代码返回一个空的 XML 元素。但为什么?

原因

根据关于 bug 报告FLEX-33664 的评论员 Justin Mclean 的说法 ,以下是罪魁祸首(请参阅我的小提琴中的最后两个测试,以验证这一点):

var thisIsNotNull:XML = <root>null</root>;
if(thisIsNotNull == null){
    // always branches here, as (thisIsNotNull == null) strangely returns true
    // despite the fact that thisIsNotNull is a valid instance of type XML
}

当将currentChild.appendChild传递给字符串"null" ,它将首先将其转换为文本为null的根 XML 元素,然后针对 null 文字对该元素进行测试。这是一个弱等式测试,因此将包含 null 的 XML 强制转换为 null 类型,或者将 null 类型强制转换为包含字符串 “null” 的根 xml 元素,并且该测试通过,可能会失败。一种解决方法是在检查 XML(或其他任何东西)是否为 “空” 时始终使用严格的相等性测试。

除了在每个可恶的 ActionScript 版本中修复此错误之外,我能想到的唯一合理的解决方法是测试字段是否为 “null” 并将其作为CDATA 值进行转义。

CDATA 值是变异整个文本值的最合适方法,否则将导致编码 / 解码问题。例如,十六进制编码是针对单个字符的。转义元素的整个文本时,首选 CDATA 值。这样做的最大原因是它保持了人类可读性。

xkcd 注释上Bobby Tables 网站提供了很好的建议,可以避免在包括ColdFusion在内的各种语言的 SQL 查询中对用户数据(在本例中为字符串 “Null”)进行不正确的解释。

从问题中尚不清楚这是否是问题的根源,并且鉴于对第一个答案的注释(包含结构中的参数)的注释中指出的解决方案似乎很可能是其他原因。

问题可能出在 Flex 的 SOAP 编码器中。尝试在 Flex 应用程序中扩展 SOAP 编码器并调试程序,以了解如何处理 null 值。

我的猜测是,它以NaN (不是数字)的形式传递。这有时会弄乱 SOAP 消息的解组过程(最值得注意的是在JBoss 5 服务器中……)。我记得扩展了 SOAP 编码器,并对 NaN 的处理方式进行了明确的检查。

@ doc_180 具有正确的概念,只不过他专注于数字,而原始的发帖人有字符串问题。

解决方案是更改mx.rpc.xml.XMLEncoder文件。这是第 121 行:

if (content != null)
        result += content;

(我查看了 Flex 4.5.1 SDK;其他版本的行号可能有所不同。)

基本上,验证失败是因为 “内容为空”,因此您的参数未添加到传出 SOAP 数据包中。从而导致丢失参数错误。

您必须扩展此类以删除验证。然后,链上的滚雪球滚滚了,将 SOAPEncoder 修改为使用修改后的 XMLEncoder,然后将 Operation 修改为使用修改后的 SOAPEncoder,然后将 WebService 修改为使用备用 Operation 类。

我花了几个小时,但我需要继续前进。可能需要一两天。

您也许可以修复 XMLEncoder 行,并进行一些猴子修补程序以使用您自己的类。

我还要补充一点,如果您切换到将 RemoteObject / AMF 与 ColdFusion 一起使用,则可以毫无问题地传递 null。


2013 年 11 月 16 日更新

我在有关 RemoteObject / AMF 的最新评论中还有一个新内容。如果您使用的是 ColdFusion 10;然后将对象上具有空值的属性从服务器端对象中删除。因此,您必须在访问属性之前检查属性是否存在,否则会遇到运行时错误。

像这样检查:

<cfif (structKeyExists(arguments.myObject,'propertyName')>
 <!--- no property code --->
<cfelse>
 <!--- handle property  normally --->
</cfif>

与 ColdFusion 9 相比,这是行为上的变化; null 属性将变成空字符串。


编辑 12/6/2013

由于存在关于如何处理 null 的问题,因此这里有一个快速的示例应用程序,用于演示字符串 “null” 将如何与保留字 null 关联。

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" initialize="application1_initializeHandler(event)">
    <fx:Script>
        <![CDATA[
            import mx.events.FlexEvent;

            protected function application1_initializeHandler(event:FlexEvent):void
            {
                var s :String = "null";
                if(s != null){
                    trace('null string is not equal to null reserved word using the != condition');
                } else {
                    trace('null string is equal to null reserved word using the != condition');
                }

                if(s == null){
                    trace('null string is equal to null reserved word using the == condition');
                } else {
                    trace('null string is not equal to null reserved word using the == condition');
                }

                if(s === null){
                    trace('null string is equal to null reserved word using the === condition');
                } else {
                    trace('null string is not equal to null reserved word using the === condition');
                }
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
</s:Application>

跟踪输出为:

空字符串不等于使用!= 条件的空保留字

空字符串不等于使用 == 条件的空保留字

空字符串不等于使用 === 条件的空保留字

将所有字符转换为其等效的十六进制实体。在这种情况下, Null将转换为&#4E;&#75;&#6C;&#6C;

ActionScript中将null值字符串化将得到字符串"NULL" 。我的怀疑是,有人认为将字符串"NULL"解码为null是一个好主意,这会导致您在此处看到损坏—可能是因为它们在传入null对象并在数据库中获取字符串时,他们不想要那样(因此也请务必检查这种错误)。

作为一种黑客,您可以考虑在客户端进行特殊处理,将 “Null” 字符串转换为永远不会发生的内容,例如 XXNULLXX,然后在服务器上进行转换。

它不是很漂亮,但是可以解决这种边界情况的问题。

好吧,我想 Flex 的 SOAP 编码器实现似乎错误地序列化了空值。将它们序列化为 String Null 似乎不是一个好的解决方案。形式上正确的版本似乎要传递一个 null 值,例如:

<childtag2 xsi:nil="true" />

因此,“Null” 的值就是有效字符串,这正是您要寻找的。

我猜想在 Apache Flex 中解决此问题并不难。我建议打开一个 Jira 问题或与 apache-flex 邮件列表的人联系。但是,这只会修复客户端。我不能说 ColdFusion 是否能够使用以这种方式编码的空值。

另请参见 Radu Cotescu 的博客文章如何在 soapUI 请求中发送空值

这很麻烦,但是假设SEARCHSTRING的最小长度(例如 2 个字符),则将SEARCHSTRING参数放在第二个字符的substring ,然后将其作为两个参数传递: SEARCHSTRING1 ("Nu")SEARCHSTRING2 ("ll").对数据库执行查询时,将它们重新Concatenate在一起。