Coldfusion onCFCRequest将XML的返回类型更改为WDDX

Coldfusion onCFCRequest将XML的返回类型更改为WDDX,xml,coldfusion,wddx,Xml,Coldfusion,Wddx,我的客户群最终离开了Coldfusion 8,因此现在我可以利用Coldfusion 9的Application.cfc->onCFCRequest事件。我有一个测试场景设置,我的结果不是我所期望的。我调用了一个方法,该方法生成一个有效的XML响应,如下所示 Response Header: Content-Type:application/xml;charset=UTF-8 Response: <?xml version="1.0" encoding="UTF-8"?> <r

我的客户群最终离开了Coldfusion 8,因此现在我可以利用Coldfusion 9的
Application.cfc->onCFCRequest
事件。我有一个测试场景设置,我的结果不是我所期望的。我调用了一个方法,该方法生成一个有效的
XML
响应,如下所示

Response Header: Content-Type:application/xml;charset=UTF-8
Response:
<?xml version="1.0" encoding="UTF-8"?>
<rows><row id="10000282742505"><cell/><cell> ...
这里是事件

<cffunction name="onCFCRequest" access="public" returntype="Any" output="true">
    <cfargument type="string" name="cfc" required="true">
    <cfargument type="string" name="method" required="true">
    <cfargument type="struct" name="args" required="true">

    <cfscript>
        // OnCFCRequest security hole fix as detailed here: http://blog.adamcameron.me/2013/04/its-easy-to-create-security-hole-in.html
        var o = createObject(ARGUMENTS.cfc);
        var metadata = getMetadata(o[ARGUMENTS.method]);

        if (structKeyExists(metadata, "access") && metadata.access == "remote"){
            return invoke(o, ARGUMENTS.method, ARGUMENTS.args);
        }else{
            throw(type="InvalidMethodException", message="Invalid method called", detail="The method #method# does not exists or is inaccessible remotely");
        }
    </cfscript>
    <cfreturn />
</cffunction>

//OnCFCRequest安全漏洞修复,如下所述:http://blog.adamcameron.me/2013/04/its-easy-to-create-security-hole-in.html
var o=createObject(ARGUMENTS.cfc);
var metadata=getMetadata(o[ARGUMENTS.method]);
if(structKeyExists(元数据,“访问”)&&metadata.access==“远程”){
返回调用(o,ARGUMENTS.method,ARGUMENTS.args);
}否则{
抛出(type=“InvalidMethodException”,message=“Invalid method called”,detail=“该方法不存在或无法远程访问”);
}
如何让onCFCRequest以远程函数返回的相同格式通过响应?

我知道这篇文章:


我可能最终会尝试这样做,但首先我想弄清楚为什么我不能简单地以相同的格式传递响应。

我从未使用过
onCfcRequest
,但你是对的,这有点愚蠢

似乎
oncfcrest
也会“吞并”
returnFormat
,因此您必须实现自己的
returnFormat
检测并序列化为正确的格式

OnCFCRequest()方法的ReturnType应该是VOID,就像 它的OnRequest()计数器部分。从这个方法返回一个值不起作用 似乎对页面中实际返回的内容没有任何影响 答复。要返回值,您必须在 方法体,或通过CFContent将其流回

引自:

例如

。。。
var result=invoke(o,ARGUMENTS.method,ARGUMENTS.args);
...
#结果#

看来我不能简单地以相同的格式传递结果,因为。。。默认情况下,ColdFusion将除XML以外的所有返回类型(包括简单返回类型)序列化为WDDX格式,并将XML数据作为XML文本返回。“()。被调用的远程函数将my
xml
作为字符串返回给
onCFCRequest
onCFCRequest
,然后将该简单返回类型(此时的字符串)转换为
WDDX
,因为这是默认行为

所以

经过大量测试后,我最终采用了Ben Nadel文章中的解决方案,但我想提到一些调整

  • 第一步是添加Adam Cameron发现的bug的问题中已经显示的代码
  • 第二个添加是在调用之后立即执行此操作:
    。在Ben的评论中,它说“…所有返回的值都是字符串…”,但事实似乎并非如此。我们有一些只返回一个数字的远程函数。如果没有
    ToString()
    ,将响应转换为二进制的代码将失败
  • 在设置mimetype的部分下面,我修改了
    json
    的IF语句。在我们编写的每个远程函数中,我们创建一个ColdFusion结构,然后像这样返回它:
    。这似乎比手动拼凑json字符串,然后在
    onCFCRequest
    中序列化要容易得多。因此,在json mimeType的onCFCRequest中,我将其视为字符串,因为它已经序列化了;所以不需要再次序列化它
  • 同样在mimeType部分,我为
    xml
    添加了一个IF语句。我们有许多远程函数,它们为网格吐出
    xml
    ,而不是
    wddx
    。由于没有
    xml的
    returnFormat
    ,我在
    wddx
    检查的正上方添加了
    xml的
    returnType
    检查
  • 根据@Henry的评论,将
    JSON
    XML
    的响应类型更改为
    application/JSON
    application/XML
    。谢谢 非常感谢本和亚当为我们奠定了基础

    这是最后的结果

    <cffunction name="onCFCRequest" access="public" returntype="void" output="true" hint="I process the user's CFC request.">
        <cfargument name="component" type="string" required="true" hint="I am the component requested by the user." />
        <cfargument name="methodName" type="string" required="true" hint="I am the method requested by the user." />
        <cfargument name="methodArguments" type="struct" required="true" hint="I am the argument collection sent by the user." />
    
        <!---
        Here we can setup any request level variables we want
        and they will be accessible to all remote cfc calls.
        --->
        <cfset request.jspath  = 'javascript'>
        <cfset request.imgpath = 'images'>
        <cfset request.csspath = 'css'>
    
        <!---
        Check to see if the target CFC exists in our cache.
        If it doesn't then, create it and cached it.
        --->
        <cfif !structKeyExists( application.apiCache, arguments.component )>
    
            <!---
            Create the CFC and cache it via its path in the
            application cache. This way, it will exist for
            the life of the application.
            --->
            <cfset application.apiCache[ arguments.component ] = createObject( "component", arguments.component ) />
        </cfif>
    
        <!---
        ASSERT: At this point, we know that the target
        component has been created and cached in the
        application.
        --->
    
        <!--- Get the target component out of the cache. --->
        <cfset local.cfc = application.apiCache[ arguments.component ] />
    
        <!--- Get the cfcs metaData --->
        <cfset var metadata = getMetaData( local.cfc[ arguments.methodName ] )>
    
        <!--- OnCFCRequest security hole fix as detailed here: http://cfmlblog.adamcameron.me/2013/04/its-easy-to-create-security-hole-in.html --->
        <cfif structKeyExists(metadata, "access") and metadata.access eq "remote">
            <!--- Good to go! --->
        <cfelse>
            <cfthrow type="InvalidMethodException" message="Invalid method called" detail="The method #arguments.methodName# does not exists or is inaccessible remotely">
        </cfif>
    
        <!---
        Execute the remote method call and store the response
        (note that if the response is void, it will destroy
        the return variable).
        --->
        <cfinvoke returnvariable="local.result" component="#local.cfc#" method="#arguments.methodName#" argumentcollection="#arguments.methodArguments#" />
    
        <!---
        We have some functions that return only a number (ex: lpitems.cfc->get_lpno_onhandqty).
        For those we must convert the number to a string, otherwise, when we try to
        convert the response to binary down at the bottom of this function it will bomb.
        --->
        <cfset local.result = ToString(local.result)>
    
        <!---
        Create a default response data variable and mime-type.
        While all the values returned will be string, the
        string might represent different data structures.
        --->
        <cfset local.responseData = "" />
        <cfset local.responseMimeType = "text/plain" />
    
        <!---
        Check to see if the method call above resulted in any
        return value. If it didn't, then we can just use the
        default response value and mime type.
        --->
        <cfif structKeyExists( local, "result" )>
    
            <!---
            Check to see what kind of return format we need to
            use in our transformation. Keep in mind that the
            URL-based return format takes precedence. As such,
            we're actually going to PARAM the URL-based format
            with the default in the function. This will make
            our logic much easier to follow.
    
            NOTE: This expects the returnFormat to be defined
            on your CFC - a "best practice" with remote
            method definitions.
            --->
            <cfparam name="url.returnFormat" type="string" default="#metadata.returnFormat#" />
            <cfparam name="url.returnType" type="string" default="#metadata.returnType#" /> <!--- Added this line so we can check for returnType of xml --->
    
            <!---
            Now that we know the URL scope will have the
            correct format, we can check that exclusively.
            --->
            <cfif (url.returnFormat eq "json")>
                <!--- Convert the result to json. --->
                <!---
                We already serializeJSON in the function being called by the user, this would cause double encoding, so just treat as text
                <cfset local.responseData = serializeJSON( local.result ) />
                --->
                <cfset local.responseData = local.result />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "application/json" />
            <!---
            There is no returnFormat of xml so we will check returnType instead.
            This leaves the door open for us to use wddx in future if we decide to.
            --->
            <cfelseif (url.returnType eq "xml")>
                <!--- Convert the result to string. --->
                <cfset local.responseData = local.result />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "application/xml" />
            <cfelseif (url.returnFormat eq "wddx")>
                <!--- Convert the result to XML. --->
                <cfwddx action="cfml2wddx" input="#local.result#" output="local.responseData" />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "application/xml" />
            <cfelse>
                <!--- Convert the result to string. --->
                <cfset local.responseData = local.result />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "text/plain" />
            </cfif>
    
        </cfif>
    
        <!---
        Now that we have our response data and mime type
        variables defined, we can stream the response back
        to the client.
        --->
    
        <!--- Convert the response to binary. --->
        <cfset local.binaryResponse = toBinary( toBase64( local.responseData ) ) />
    
        <!---
        Set the content length (to help the client know how
        much data is coming back).
        --->
        <cfheader name="content-length" value="#arrayLen( local.binaryResponse )#" />
    
        <!--- Stream the content. --->
        <cfcontent type="#local.responseMimeType#" variable="#local.binaryResponse#" />
    
        <!--- Return out. --->
        <cfreturn />
    </cffunction>
    
    
    
    谢谢@Henry。我想我找到了我的
    returnFormat
    被更改的原因,或者你所说的“吞没”的原因:我不能简单地用相同的格式传递结果,因为。。。“默认情况下,ColdFusion将所有返回类型(包括简单返回类型)(XML除外)序列化为WDDX格式…”本例中调用的远程函数将my
    XML
    作为
    字符串返回给
    onCFCRequest
    onCFCRequest
    然后转换该简单返回类型(此时的字符串)进入
    WDDX
    ,因为这是默认行为。我同意这一点,只是很高兴现在对我来说更清楚了一点:)@Gordon one会认为CF足够聪明,可以在整个链中携带returnformat,这样像您这样的人就不需要跳过那么多障碍了。
    application/xml
    是首选:<代码>应用程序/json
    是首选:谢谢!我更改了它们,并在IE7及以上版本、Chrome、Firefox和Safari中进行了测试。一切都很好。你知道,在我完成这个练习之前,我总是将我的
    JSON
    作为
    text/plain返回
    
    ...
    var result = invoke(o, ARGUMENTS.method, ARGUMENTS.args);
    ...
    <!--- after your </cfscript> --->
    
    <!--- TODO: do some checking to determine the preferred return type --->
    
    <!--- if detected as xml, serve as xml (safer option) --->
    <cfcontent type="application/xml" 
               variable="#toBinay(toBase64(local.result))#">
    
    <!--- *OR* (cleaner version) watch out for white spaces --->
    <cfcontent type="application/xml">
    <cfoutput>#result#</cfoutput>
    
    <cffunction name="onCFCRequest" access="public" returntype="void" output="true" hint="I process the user's CFC request.">
        <cfargument name="component" type="string" required="true" hint="I am the component requested by the user." />
        <cfargument name="methodName" type="string" required="true" hint="I am the method requested by the user." />
        <cfargument name="methodArguments" type="struct" required="true" hint="I am the argument collection sent by the user." />
    
        <!---
        Here we can setup any request level variables we want
        and they will be accessible to all remote cfc calls.
        --->
        <cfset request.jspath  = 'javascript'>
        <cfset request.imgpath = 'images'>
        <cfset request.csspath = 'css'>
    
        <!---
        Check to see if the target CFC exists in our cache.
        If it doesn't then, create it and cached it.
        --->
        <cfif !structKeyExists( application.apiCache, arguments.component )>
    
            <!---
            Create the CFC and cache it via its path in the
            application cache. This way, it will exist for
            the life of the application.
            --->
            <cfset application.apiCache[ arguments.component ] = createObject( "component", arguments.component ) />
        </cfif>
    
        <!---
        ASSERT: At this point, we know that the target
        component has been created and cached in the
        application.
        --->
    
        <!--- Get the target component out of the cache. --->
        <cfset local.cfc = application.apiCache[ arguments.component ] />
    
        <!--- Get the cfcs metaData --->
        <cfset var metadata = getMetaData( local.cfc[ arguments.methodName ] )>
    
        <!--- OnCFCRequest security hole fix as detailed here: http://cfmlblog.adamcameron.me/2013/04/its-easy-to-create-security-hole-in.html --->
        <cfif structKeyExists(metadata, "access") and metadata.access eq "remote">
            <!--- Good to go! --->
        <cfelse>
            <cfthrow type="InvalidMethodException" message="Invalid method called" detail="The method #arguments.methodName# does not exists or is inaccessible remotely">
        </cfif>
    
        <!---
        Execute the remote method call and store the response
        (note that if the response is void, it will destroy
        the return variable).
        --->
        <cfinvoke returnvariable="local.result" component="#local.cfc#" method="#arguments.methodName#" argumentcollection="#arguments.methodArguments#" />
    
        <!---
        We have some functions that return only a number (ex: lpitems.cfc->get_lpno_onhandqty).
        For those we must convert the number to a string, otherwise, when we try to
        convert the response to binary down at the bottom of this function it will bomb.
        --->
        <cfset local.result = ToString(local.result)>
    
        <!---
        Create a default response data variable and mime-type.
        While all the values returned will be string, the
        string might represent different data structures.
        --->
        <cfset local.responseData = "" />
        <cfset local.responseMimeType = "text/plain" />
    
        <!---
        Check to see if the method call above resulted in any
        return value. If it didn't, then we can just use the
        default response value and mime type.
        --->
        <cfif structKeyExists( local, "result" )>
    
            <!---
            Check to see what kind of return format we need to
            use in our transformation. Keep in mind that the
            URL-based return format takes precedence. As such,
            we're actually going to PARAM the URL-based format
            with the default in the function. This will make
            our logic much easier to follow.
    
            NOTE: This expects the returnFormat to be defined
            on your CFC - a "best practice" with remote
            method definitions.
            --->
            <cfparam name="url.returnFormat" type="string" default="#metadata.returnFormat#" />
            <cfparam name="url.returnType" type="string" default="#metadata.returnType#" /> <!--- Added this line so we can check for returnType of xml --->
    
            <!---
            Now that we know the URL scope will have the
            correct format, we can check that exclusively.
            --->
            <cfif (url.returnFormat eq "json")>
                <!--- Convert the result to json. --->
                <!---
                We already serializeJSON in the function being called by the user, this would cause double encoding, so just treat as text
                <cfset local.responseData = serializeJSON( local.result ) />
                --->
                <cfset local.responseData = local.result />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "application/json" />
            <!---
            There is no returnFormat of xml so we will check returnType instead.
            This leaves the door open for us to use wddx in future if we decide to.
            --->
            <cfelseif (url.returnType eq "xml")>
                <!--- Convert the result to string. --->
                <cfset local.responseData = local.result />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "application/xml" />
            <cfelseif (url.returnFormat eq "wddx")>
                <!--- Convert the result to XML. --->
                <cfwddx action="cfml2wddx" input="#local.result#" output="local.responseData" />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "application/xml" />
            <cfelse>
                <!--- Convert the result to string. --->
                <cfset local.responseData = local.result />
                <!--- Set the appropriate mime type. --->
                <cfset local.responseMimeType = "text/plain" />
            </cfif>
    
        </cfif>
    
        <!---
        Now that we have our response data and mime type
        variables defined, we can stream the response back
        to the client.
        --->
    
        <!--- Convert the response to binary. --->
        <cfset local.binaryResponse = toBinary( toBase64( local.responseData ) ) />
    
        <!---
        Set the content length (to help the client know how
        much data is coming back).
        --->
        <cfheader name="content-length" value="#arrayLen( local.binaryResponse )#" />
    
        <!--- Stream the content. --->
        <cfcontent type="#local.responseMimeType#" variable="#local.binaryResponse#" />
    
        <!--- Return out. --->
        <cfreturn />
    </cffunction>