AMF как формат REST, используя BlazeDS и URLLoader AS3

#actionscript-3 #rest #coldfusion #blazeds #amf

#actionscript-3 #rest #coldfusion #blazeds #amf

Вопрос:

У меня есть сервер ColdFusion с HTTP API, который в настоящее время возвращает ответы в формате JSON или XML. Внутренне все эти ответы представляются с использованием типа ColdFusion ‘struct’, а затем преобразуются в соответствующий формат перед отправкой запрашивающему клиенту.

Команда клиентов, использующая этот API, запросила, чтобы в дополнение к JSON и XML мы также возвращали объекты AMF. Итак, я видел (и сам привел) аргументы против использования AMF в качестве «возвращаемого формата» в сценарии REST, а не в качестве формата RPC. Меня не интересуют комментарии о том, хорошая это идея или нет — я просто хотел бы знать, сработает ли это. Если нет, я надеюсь по понятным причинам, почему этого не произойдет. Если это сработает, я хотел бы получить некоторые рекомендации о том, как ‘makeitgo’.

Итак, в интересах достижения этого примера доказательства концепции, я пытаюсь сериализовать двухэлементный массив ColdFusion с помощью BlazeDS, затем использовать этот сериализованный объект в тестировании 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 «Предоставленный индекс выходит за пределы». Я искал распространенные причины этой ошибки, но не нашел никаких четких ответов. Я попытался сбросить ByteArray.position как в начало, так и в конец ByteArray, прежде чем пытаться выполнить readObject() — изменение его на end выдает ошибку # 2030 «Обнаружен конец файла» (как и следовало ожидать), но я проверил, что .position по умолчанию равен 0, что генерирует ошибку # 2006.

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(); 
  

Основное отличие здесь в том, что вместо того, чтобы пытаться сериализовать массив 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: out of bounds. Свойство ByteArray.length одинаково в обоих случаях AMF0 и AMF3, но содержимое возвращаемого объекта отличается (при просмотре в шестнадцатеричном редакторе).

Изменение objectEncoding в первом примере, который я предоставил, не повлияло на создаваемую ошибку.

Итак, тогда проблема, похоже, заключалась в попытке сериализовать массив ColdFusion: AMFSerializer не знает, как с этим справиться. Он должен быть явно создан как ASObject. Я создам функцию очистки, чтобы выполнить преобразование между двумя типами.

Я чувствую, что достигнут прогресс (и спасибо за все отзывы в комментариях и ответах), но у меня все еще есть много вопросов без ответов. У кого-нибудь есть какие-либо данные о том, почему это может привести к сбою при попытке кодирования в AMF3, но не для AMF0? У меня нет привязанности ни к одному, ни к другому, но мне не нравится этот метод решения проблемы «бросать вещи в стену и смотреть, какие прилипают»… Я хотел бы знать, почему это сбой =/

Комментарии:

1. Ошибка, из-за которой указанный индекс выходит за пределы … проследите длину двоичного объекта, возвращенного из запроса. Вероятно, это 0, что означает, что вы возвращаете либо «пустой» bytearray, либо виртуальная машина flash ничего не возвращает из запроса и, будучи flash, вместо возврата нулевого массива она автоматически генерирует его в какой-то момент. Опять же, проследите длину bytearray, возвращаемого на стороне флэш-памяти.

2. Также я не специалист по CF, но я не вижу, куда вы записываете свои объекты AMF в выходной поток. Я вижу, что вы получаете ссылку на него, а затем закрываете эту ссылку, не записывая в нее. Простите меня, если я ошибаюсь, но по чистой логике я предполагаю, что в какой-то момент процесса вы должны записать свои данные в поток вывода ответа.

3. Кто-то в Adobe создал нечто подобное, об этом есть интересное обсуждение [здесь][1] [1]: bugs.adobe.com/jira/browse/BLZ-209

4. @Ascension Systems: Байтовый массив нулевой длины не является проблемой; Проверка того, что были возвращены фактические данные и что байтовый массив действительно был заполнен, была одной из первых вещей, которые я сделал. Тем не менее, хороший совет.

5. @Ascension Systems: объект записывается в выходной поток через объект AmfMessageSerializer. Когда это инициализировано, вы привязываете его к потоку. Вызов writeObject() выполняет фактическую запись в поток.

Ответ №1:

Я сделал это некоторое время назад .. вы можете проверить мой пост в блоге здесь, возможно, это сможет вам помочь. Я использовал Java на стороне сервера, а не CF.

Комментарии:

1. Здесь много полезной информации. Использование Java само по себе не является проблематичным; все материалы BlazeDS в любом случае являются Java (CF использует ‘CreateObject’ для создания экземпляра объекта Java; вы заметите, что это происходит в моем минимальном примере). Я надеюсь избежать развертывания своих собственных классов, чтобы снизить сложность кода, но в конечном итоге это может оказаться единственным выходом. Спасибо за ответ!