Coldfusion onCFCRequest Изменение типа возврата XML в 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"?>
<rows><row id="10000282742505"><cell/><cell> ...
Теперь, после того как я представлю onCFCRequest
Если я получу это обратно (что сломает мои сетки)...
Response Header: Content-Type:application/xml;charset=UTF-8
Response:
<wddxPacket version='1.0'><header/><data><string><rows><row id="10000282742505"><cell></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://www.bennadel.com/blog/1647-Learning-ColdFusion-9-Application-cfc-OnCFCRequest-Event-Handler-For-CFC-Requests.htm
Я, вероятно, в конечном итоге попробую это, но сначала я хотел бы выяснить, почему я не могу просто пройти через ответ в том же формате.
2 ответа
Похоже, я не могу просто пропустить результат с тем же форматом из-за этого... "По умолчанию ColdFusion сериализует все возвращаемые типы (включая простые возвращаемые типы), кроме XML, в формат WDDX и возвращает данные XML в виде XML текст."( http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-7f5c.html). Вызываемая удаленная функция возвращает мой xml
как строка в onCFCRequest
, onCFCRequest
затем преобразует этот простой тип возвращаемого значения (строку в этой точке) в WDDX
потому что это поведение по умолчанию.
Так...
После долгих испытаний я решил использовать решение из статьи Бена Наделя, но с некоторыми изменениями, о которых я хотел бы упомянуть.
- Первым добавлением было добавление кода, уже показанного в моем вопросе, для найденной Адамом Кэмероном ошибки.
- Второе дополнение должно было сделать это сразу после вызова:
<cfset local.result = ToString(local.result)>
, В комментариях Бена говорится: "... все возвращаемые значения будут строковыми...", но это не так. У нас есть несколько удаленных функций, которые только возвращают число. БезToString()
код внизу, который преобразует ответ в двоичный код, терпит неудачу. - Внизу в разделе, где устанавливаются mimeTypes, я изменил оператор IF для
json
, В каждой написанной нами удаленной функции мы создаем структуру ColdFusion, а затем возвращаем ее так:<cfreturn SerializeJSON(z.response) />
, Это кажется намного проще, чем вручную соединять строку json, затем сериализовать ее вonCFCRequest
, Так что в onCFCRequest для json mimeType я просто воспринимаю его как строку, потому что он уже сериализован; поэтому не нужно сериализовать его во второй раз. - Также в разделе mimeType я добавил оператор IF для
xml
, У нас есть много удаленных функций, которые выплевываютxml
для сетки, а неwddx
, И так как нетreturnFormat
изxml
Я добавил чек наreturnType
изxml
прямо надwddx
проверять. - Изменен responseMimeType для
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>
Я никогда не использовал onCfcRequest
но ты прав, это как-то глупо
Похоже на onCfcRequest
также "проглотит" returnFormat
Поэтому вы должны реализовать свой собственный returnFormat
обнаружение и сериализация в правильном формате.
ReturnType метода OnCFCRequest() должен быть VOID так же, как его аналог OnRequest(). Возвращение значения из этого метода, похоже, не играет никакой роли в том, что фактически возвращается в ответе страницы. Чтобы вернуть значение, вы должны либо вывести его в теле метода, либо передать его обратно через CFContent.
Цитируется по адресу: http://www.bennadel.com/blog/1647-Learning-ColdFusion-9-Application-cfc-OnCFCRequest-Event-Handler-For-CFC-Requests.htm
Например
...
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>