AMF 作为 REST 格式,使用 BlazeDS 和 AS3 的 URLLoader
我有一个带有 HTTP API 的 ColdFusion 服务器,当前返回 JSON 或 XML 格式的响应。在内部,所有这些响应都使用 ColdFusion“结构”类型表示,然后在呈现给请求客户端之前转换为适当的格式。
使用此 API 的客户端团队要求除了 JSON 和 XML 之外,我们还返回 AMF 对象。现在,我已经看到(并且我自己也提出了)反对在 REST 场景中使用 AMF 作为“返回格式”而不是作为 RPC 格式的论点。我对关于这是否是个好主意的评论不感兴趣——我只是想知道它是否有效。如果没有,我希望有明确的理由说明为什么不会。如果它有效,我希望得到一些关于如何“makeitgo”的指导。
因此,为了实现概念验证示例,我尝试使用 BlazeDS 序列化一个二元素 ColdFusion 数组,然后在 Flash Player 10/AS3 测试中使用该序列化对象。
这是服务器端代码的样子:
//the test object I'm trying to return (keeping it simple)
var testArray = ArrayNew(1);
testArray[1]="test";
testArray[2]="monkey";
//set up output stream to requesting client
var pc = getPageContext();
var response = pc.getResponse();
var out = response.getOutputStream();
response.setHeader("Content-Type", "application/x-amf");
//not sure if AmfMessageSerializer is the appropriate class to be using here
var amfMessageSerializer = createObject("java", "flex.messaging.io.amf.AmfMessageSerializer");
var amfTrace = createObject("java", "flex.messaging.io.amf.AmfTrace"); //needed by initialize() method
amfMessageSerializer.initialize(variables.SerializationContext.getSerializationContext(), out, amfTrace);
amfMessageSerializer.writeObject(testArray);
out.close();
现在,这会生成某种类型的二进制数据。如果我将其粘贴在 .cfm 中并尝试加载页面,我会得到一些可以在十六进制编辑器中读取的内容,它看起来包含我设置的数组成员。这里需要注意一点:响应消息的一部分包括“flex.messaging.io.ArrayCollection”。我还不够了解这告诉我什么:任何能够提供有关在两个环境之间键入的详细信息的人都会非常感谢我。
下一步是尝试在 FlashPlayer 端使用它。下面是精简后的 AS3 的样子:
myURLloader.dataFormat = URLLoaderDataFormat.BINARY;
myURLloader.addEventListener(Event.COMPLETE, completeHandler);
myURLloader.load(myURLRequest);
function completeHandler( event : Event) : void
{
var serverResponse : ByteArray = new ByteArray();
serverResponse = event.target.data;
//read the response into a generic object
var responseObject : Object = new Object();
responseObject = serverResponse.readObject(); //fails here: error #2006
}
如注释所示,此操作失败并出现错误 #2006“提供的索引超出范围”。我已经搜索了此错误的常见原因,但没有找到任何明确的答案。在尝试 readObject() 之前,我尝试将 byteArray.position 重置为 byteArray 的开头和结尾 - 将其更改为结尾会出现错误 #2030“遇到文件结尾”(正如人们所期望的那样),但我已经验证 .position 默认为 0,这会生成 #2006 错误。
我很确定这里的问题在于我使用的 BlazeDS 调用的选择;我想当我想序列化一个对象时我可能会序列化一条消息。不幸的是,BlazeDS 的 JavaDoc 自动生成的文档……缺乏启发性。我发现的所有更具可读性的资源都集中在 Flash Remoting 和 RPC 示例上。令人惊讶的是,我知道;但事实就是如此。我正在使用 Adobe BlazeDS 文档;如果其他人有更好的资源,我将非常感激。
提醒所有回答的人:我意识到这不是 AMF 的典型用途。我对建议典型 RPC 方法或切换到 Flash Remoting 而不是 HTTP/GET 的响应不感兴趣。我需要的是来自 HTTP 请求的 AMF 序列化响应,该响应可以在 Flash Player 客户端上反序列化。在这件事上我别无选择。我确实需要知道这是否可行,如果可以,我希望获得一些关于如何使其发挥作用的指导。除了“不要使用 AMF!”之外,我欢迎任何和所有建议。或“只需切换到 Flash Remoting!”
更新:在此方面取得了一些进展:
1)在服务器端,我使用blazeDS的ASObject类创建一个简单的ASObject并用键值对填充它。
2)在客户端和服务器端,我必须确保将对象编码设置为 AMF0。 AMF3 中的相同技术会生成 #2006/Out ofbounds 错误,我还不知道为什么。
现在服务器端的代码如下所示:
//set up output stream to requesting client
var pc = getPageContext();
var response = pc.getResponse();
var out = response.getOutputStream();
response.setHeader("Content-Type", "application/x-amf");
//not sure if AmfMessageSerializer is the appropriate class to be using here
var amfMessageSerializer = createObject("java", "flex.messaging.io.amf.AmfMessageSerializer");
amfMessageSerializer.setVersion(variables.MessageIOConstants.AMF0);
var amfTrace = createObject("java", "flex.messaging.io.amf.AmfTrace"); //needed by initialize() method
amfMessageSerializer.initialize(variables.SerializationContext.getSerializationContext(), out, amfTrace);
var ASObject = createObject("java", "flex.messaging.io.amf.ASObject");
ASObject.put("testKey", "testValue"); //simple key-value map to return to caller
amfMessageSerializer.writeObject(testArray);
out.close();
这里的主要区别是,我不是尝试序列化 CF 数组,而是手动构建响应对象(ASObject 类型)。
在客户端,代码现在如下所示:
myURLloader.dataFormat = URLLoaderDataFormat.BINARY;
myURLloader.addEventListener(Event.COMPLETE, completeHandler);
myURLloader.load(myURLRequest);
function completeHandler( event : Event) : void
{
var serverResponse : ByteArray = new ByteArray();
serverResponse = event.target.data;
serverResponse.objectEncoding = ObjectEncoding.AMF0; //important
//read the response into a generic object
var responseObject : Object = new Object();
responseObject = serverResponse.readObject();
trace(responseObject.testKey); //displays "testValue", as expected
}
此处的区别在于我已将 ObjectEncoding 显式设置为 AMF0(默认为 AMF3)。
如果我在服务器和客户端上将 objectEncoding 切换为 AMF3,我希望一切正常,但我仍然收到 2006:越界错误。 AMF0 和 AMF3 情况下的 ByteArray.length 属性相同,但返回对象的内容不同(在十六进制编辑器中查看时)。
更改我提供的第一个示例中的 objectEncoding 对所产生的错误没有影响。
那么,问题似乎是尝试序列化 ColdFusion 数组:AMFSerializer 不知道如何处理它。它需要显式构建为 ASObject。我将构建一个清理函数来执行两种类型之间的转换。
我觉得已经取得了进展(并感谢评论和答案中的所有反馈),但我仍然有很多未解答的问题。有谁知道为什么当我尝试在 AMF3 中编码时可能会失败,但在 AMF0 中不会?我对其中之一没有任何依恋,但我不喜欢这种“把东西扔到墙上,看看哪些粘住”的解决问题的方法......我想知道为什么它失败了=/
I have a ColdFusion server with an HTTP API that's currently returning either JSON or XML formatted responses. Internally, all of these responses are represented using the ColdFusion 'struct' type, and then converted to the appropriate format before being presented to the requesting client.
The client team using this API has requested that in addition to JSON and XML, we also return AMF objects. Now, I've seen (and have myself made) arguments against using AMF as a 'returned format' in a REST scenario rather than as an RPC format. I'm not interested in comments about whether or not it's a good idea -- I'd just like to know if it will work. If not, I'm hoping for clear reasons why it won't. If it will work, I'd love some guidance on how to 'makeitgo'.
So, in the interest of achieving that proof of concept example, I'm trying to serialize a two-element ColdFusion Array using BlazeDS, then consume that serialized object in a Flash Player 10/AS3 test.
Here's what the code looks like on the server side:
//the test object I'm trying to return (keeping it simple)
var testArray = ArrayNew(1);
testArray[1]="test";
testArray[2]="monkey";
//set up output stream to requesting client
var pc = getPageContext();
var response = pc.getResponse();
var out = response.getOutputStream();
response.setHeader("Content-Type", "application/x-amf");
//not sure if AmfMessageSerializer is the appropriate class to be using here
var amfMessageSerializer = createObject("java", "flex.messaging.io.amf.AmfMessageSerializer");
var amfTrace = createObject("java", "flex.messaging.io.amf.AmfTrace"); //needed by initialize() method
amfMessageSerializer.initialize(variables.SerializationContext.getSerializationContext(), out, amfTrace);
amfMessageSerializer.writeObject(testArray);
out.close();
Now, this generates some kind of binary data. If I stick that in a .cfm and try to load the page, I get something I can read in a hex editor that looks like it contains the array members I set. One note here: part of the response message includes "flex.messaging.io.ArrayCollection." I'm not knowledgeable enough to know what this tells me yet: anyone who can provide details about typing between the two environments will have many thanks from me.
The next step is to try and consume this on the FlashPlayer side. Here's what the stripped down AS3 looks like:
myURLloader.dataFormat = URLLoaderDataFormat.BINARY;
myURLloader.addEventListener(Event.COMPLETE, completeHandler);
myURLloader.load(myURLRequest);
function completeHandler( event : Event) : void
{
var serverResponse : ByteArray = new ByteArray();
serverResponse = event.target.data;
//read the response into a generic object
var responseObject : Object = new Object();
responseObject = serverResponse.readObject(); //fails here: error #2006
}
As indicated by the comment, this fails with error #2006 "Supplied index is out of bounds." I've searched around for common causes of this error, but haven't found any clear answers. I've tried resetting the byteArray.position to both the beginning and the end of the byteArray before attempting readObject() -- changing it to the end spits out error #2030 "End of file was encountered" (as one might expect), but I've verified that the .position defaults to 0, which generates the #2006 error.
I'm pretty sure that the issue here lies with the choice of BlazeDS calls I've used; I think I might be serializing a message when I want to be serializing an object. Unfortunately, the JavaDoc autogenerated docs for BlazeDS are ... less than enlightening. All of the more readable resources I've found focus on Flash Remoting and RPC examples. Surprising, I know; but it is what it is. I'm using Adobe BlazeDS docs; if anyone else has a better resource I'd be quite appreciative.
As a reminder to anyone answering: I realize this isn't a typical use of AMF. I'm not interested in responses that suggest the typical RPC method or to switch to Flash Remoting rather than HTTP/GET. What I need is an AMF serialized response from an HTTP request which can be deserialized on a Flash Player client. I don't have a choice in this matter. I do need to know if this is possible, and if so I'm hoping for some guidance on how to make it work. I welcome any and all suggestions aside from "Just don't use AMF!" or "Just switch to Flash Remoting!"
Update: made a little bit of progress with this:
1) on the server side, I used the ASObject class of blazeDS to create a simple ASObject and populate it with a key-value pair.
2) on both the client and server side, I had to make sure to set the object encoding to AMF0. The same technique in AMF3 generates that #2006/Out of bounds error, and I'm not yet sure why.
Here's what the code now looks like on the server-side:
//set up output stream to requesting client
var pc = getPageContext();
var response = pc.getResponse();
var out = response.getOutputStream();
response.setHeader("Content-Type", "application/x-amf");
//not sure if AmfMessageSerializer is the appropriate class to be using here
var amfMessageSerializer = createObject("java", "flex.messaging.io.amf.AmfMessageSerializer");
amfMessageSerializer.setVersion(variables.MessageIOConstants.AMF0);
var amfTrace = createObject("java", "flex.messaging.io.amf.AmfTrace"); //needed by initialize() method
amfMessageSerializer.initialize(variables.SerializationContext.getSerializationContext(), out, amfTrace);
var ASObject = createObject("java", "flex.messaging.io.amf.ASObject");
ASObject.put("testKey", "testValue"); //simple key-value map to return to caller
amfMessageSerializer.writeObject(testArray);
out.close();
The primary difference here is that rather than trying to serialize a CF Array, I'm building a response object (of type ASObject) manually.
On the client side, the code now looks like this:
myURLloader.dataFormat = URLLoaderDataFormat.BINARY;
myURLloader.addEventListener(Event.COMPLETE, completeHandler);
myURLloader.load(myURLRequest);
function completeHandler( event : Event) : void
{
var serverResponse : ByteArray = new ByteArray();
serverResponse = event.target.data;
serverResponse.objectEncoding = ObjectEncoding.AMF0; //important
//read the response into a generic object
var responseObject : Object = new Object();
responseObject = serverResponse.readObject();
trace(responseObject.testKey); //displays "testValue", as expected
}
The difference here is that I've explicitly set the ObjectEncoding to AMF0 (defaults to AMF3).
If I switch the objectEncoding to AMF3 on both server and client, I'd expect things to work, but I still get the 2006: out of bounds error. The ByteArray.length property is the same in both the AMF0 and AMF3 cases, but the content of the returned object is different (when viewed in a hex editor).
Changing the objectEncoding in the first example I provided had no effect on the error that was being produced.
So, then, the issue seems to have been an attempt to serialize the ColdFusion array: the AMFSerializer doesn't know how to handle it. It needs to explicitly be built as an ASObject. I'll build a sanitize function to do the conversion between the two types.
I feel like progress has been made (and thanks for all the feedback in comments and answers), but I've still got a lot of unanswered questions. Does anyone have any input on why this might be failing when I try to encode in AMF3, but not for AMF0? I don't have any attachment to one or the other, but I don't like this 'throw things at the wall and see which ones stick' method of solving the problem... I'd like to know why it's failing =/
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我不久前就这样做了..您可以从 查看我的博客文章这里,也许对你有帮助。我在服务器端使用Java,而不是CF。
I did that a some time ago..you can check my blog post from here, maybe it can help you. I was using Java on the server side, not CF.