TigerZF
🌐Español

13.2. Zend_Amf_Server

Zend_Amf_Server proporciona un servidor de estilo RPC para manejar peticiones realizadas desde Adobe Flash Player utilizando el protocolo AMF. Al igual que todas las clases de servidor de Zend Framework, sigue la API de SoapServer, proporcionando una interfaz fácil de recordar para crear servidores.

Ejemplo 13.1. Servidor AMF básico

Supongamos que ha creado una clase Foo con varios métodos públicos. Puede crear un servidor AMF usando el siguiente código:

$server = new Zend_Amf_Server();
$server->setClass('Foo');
$response = $server->handle();
echo $response;

Alternativamente, puede optar por adjuntar una función simple como callback en su lugar:

$server = new Zend_Amf_Server();
$server->addFunction('myUberCoolFunction');
$response = $server->handle();
echo $response;

También podría combinar múltiples clases y funciones. Al hacerlo, sugerimos asignar un espacio de nombres a cada una para asegurar que no ocurran colisiones de nombres de métodos; esto se puede hacer simplemente pasando un segundo argumento de tipo string a addFunction() o setClass():

$server = new Zend_Amf_Server();
$server->addFunction('myUberCoolFunction', 'my')
       ->setClass('Foo', 'foo')
       ->setClass('Bar', 'bar');
$response = $server->handle();
echo $response;

Zend_Amf_Server también permite que los servicios se carguen dinámicamente a partir de una ruta de directorio proporcionada. Puede añadir tantos directorios como desee al servidor. El orden en que añada los directorios al servidor será el orden en el que se realizará la búsqueda LIFO en los directorios para coincidir con la clase. La adición de directorios se realiza con el método addDirectory().

$server->addDirectory(dirname(__FILE__) .'/../services/');
$server->addDirectory(dirname(__FILE__) .'/../package/');

Al llamar a servicios remotos, el nombre de origen puede tener guión bajo ("_") y punto (".") como delimitadores de directorio. Cuando se utiliza un guión bajo se respetarán las convenciones de nomenclatura de clases de PEAR y Zend Framework. Esto significa que si llama al servicio com_Foo_Bar el servidor buscará el archivo Bar.php en cada una de las rutas incluidas en com/Foo/Bar.php. Si se utiliza la notación de puntos para su servicio remoto, como com.Foo.Bar, a cada ruta incluida se le añadirá com/Foo/Bar.php al final para autocargar Bar.php

Todas las peticiones AMF enviadas al script serán entonces gestionadas por el servidor, y se devolverá una respuesta AMF.


[Note] Todos los métodos y funciones adjuntos necesitan docblocks

Al igual que todos los demás componentes de servidor de Zend Framework, debe documentar los métodos de su clase utilizando docblocks de PHP. Como mínimo, debe proporcionar anotaciones para cada argumento requerido, así como el valor de retorno. Como ejemplos:

// Function to attach:

/**
 * @param  string $name
 * @param  string $greeting
 * @return string
 */
function helloWorld($name, $greeting = 'Hello')
{
    return $greeting . ', ' . $name;
}
// Attached class

class World
{
    /**
     * @param  string $name
     * @param  string $greeting
     * @return string
     */
    public function hello($name, $greeting = 'Hello')
    {
        return $greeting . ', ' . $name;
    }
}

Se pueden usar otras anotaciones, pero serán ignoradas.

13.2.1. Conexión al servidor desde Flex

Conectarse a su Zend_Amf_Server desde su proyecto Flex es bastante sencillo; simplemente necesita apuntar su URI de endpoint a su script de Zend_Amf_Server.

Digamos, por ejemplo, que ha creado su servidor y lo ha colocado en el archivo server.php en la raíz de su aplicación, y por lo tanto la URI es http://example.com/server.php. En este caso, modificaría su archivo services-config.xml para establecer el atributo de uri de endpoint del canal a este valor.

Si nunca ha creado un archivo service-config.xml, puede hacerlo abriendo su proyecto en la ventana Navigator. Haga clic derecho en el nombre del proyecto y seleccione 'properties'. En el diálogo de propiedades del proyecto, entre en el menú 'Flex Build Path', en la pestaña 'Library path' y asegúrese de que el archivo 'rpc.swc' se añade a la ruta de su proyecto, y pulse Ok para cerrar la ventana.

También necesitará indicarle al compilador que use el archivo service-config.xml para encontrar el endpoint de RemoteObject. Para ello abra de nuevo el panel de propiedades del proyecto haciendo clic derecho en la carpeta del proyecto desde su Navigator y seleccionando propiedades. En el popup de propiedades seleccione 'Flex Compiler' y añada la cadena: -services "services-config.xml". Pulse Apply y luego OK para volver y actualizar la opción. Lo que acaba de hacer es indicarle al compilador de Flex que busque en el archivo services-config.xml las variables en tiempo de ejecución que utilizará la clase RemotingObject.

Ahora necesitamos indicarle a Flex qué archivo de configuración de servicios utilizar para conectarse a nuestros métodos remotos. Por esta razón, cree un nuevo archivo 'services-config.xml' en la carpeta src de su proyecto Flex. Para hacer esto, haga clic derecho en la carpeta del proyecto y seleccione 'new' 'File', lo que abrirá una nueva ventana. Seleccione la carpeta del proyecto y luego nombre el archivo 'services-config.xml' y pulse finish.

Flex ha creado el nuevo services-config.xml y lo tiene abierto. Utilice el siguiente texto de ejemplo para su archivo services-config.xml. Asegúrese de actualizar su endpoint para que coincida con el de su servidor de pruebas. Asegúrese de guardar el archivo.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="zend-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="zend">
                <channels>
                    <channel ref="zend-endpoint"/>
                </channels>
                <properties>
                    <source>*</source>
                </properties>
            </destination>
        </service>
    </services>
    <channels>
        <channel-definition id="zend-endpoint"
            class="mx.messaging.channels.AMFChannel">
            <endpoint uri="http://example.com/server.php"
                class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>
</services-config>

Hay dos puntos clave en el ejemplo. Primero, pero el último en el listado, creamos un canal AMF, y especificamos el endpoint como la URL de nuestro Zend_Amf_Server:

<channel-definition id="zend-endpoint"
    <endpoint uri="http://example.com/server.php"
        class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>

Observe que le hemos dado a este canal un identificador, "zend-endpoint". El ejemplo crea un destino de servicio que hace referencia a este canal, asignándole también un ID -- en este caso "zend".

Dentro de nuestros archivos MXML de Flex, necesitamos vincular un RemoteObject al servicio. En MXML, esto podría hacerse de la siguiente manera:

<mx:RemoteObject id="myservice"
    fault="faultHandler(event)"
    showBusyCursor="true"
    destination="zend">

Aquí, hemos definido un nuevo objeto remoto identificado como "myservice" vinculado al destino de servicio "zend" que definimos en el archivo services-config.xml. Luego llamamos a métodos en él en nuestro ActionScript simplemente llamando a "myservice.<method>". Como ejemplo:

myservice.hello("Wade");

Al asignar un espacio de nombres, utilizaría "myservice.<namespace>.<method>":

myservice.world.hello("Wade");

Para más información sobre la invocación de RemoteObject en Flex, visite el sitio de ayuda de Adobe Flex 3.

13.2.2. Manejo de errores

Por defecto, todas las excepciones lanzadas en sus clases o funciones adjuntas serán capturadas y devueltas como ErrorMessages de AMF. Sin embargo, el contenido de estos objetos ErrorMessage variará según si el servidor está o no en modo "producción" (el estado por defecto).

Cuando está en modo producción, solo se devolverá el código de la excepción. Si deshabilita el modo producción -- algo que solo debería hacerse para pruebas -- se devolverán la mayoría de los detalles de la excepción: se adjuntarán el mensaje de la excepción, la línea y el backtrace.

Para deshabilitar el modo producción, haga lo siguiente:

$server->setProduction(false);

Para volver a habilitarlo, pase un valor booleano TRUE en su lugar:

$server->setProduction(true);
[Note] ¡Deshabilite el modo producción con moderación!

Recomendamos deshabilitar el modo producción solo durante el desarrollo. Los mensajes de excepción y los backtraces pueden contener información sensible del sistema a la que no desee que accedan terceros. Aunque AMF es un formato binario, la especificación es ahora abierta, lo que significa que cualquiera puede potencialmente deserializar el payload.

Un área a la que hay que prestar especial atención son los propios errores de PHP. Cuando la directiva INI display_errors está habilitada, cualquier error de PHP del nivel de reporte de errores actual se renderiza directamente en la salida -- lo que puede alterar el payload de la respuesta AMF. Sugerimos desactivar la directiva display_errors en producción para evitar tales problemas

13.2.3. Respuestas AMF

Ocasionalmente puede desear manipular ligeramente el objeto de respuesta, típicamente para devolver cabeceras de mensaje adicionales. El método handle() del servidor devuelve el objeto de respuesta, permitiéndole hacerlo.

Ejemplo 13.2. Añadir cabeceras de mensaje a la respuesta AMF

En este ejemplo, añadimos una MessageHeader 'foo' con el valor 'bar' a la respuesta antes de devolverla.

$response = $server->handle();
$response->addAmfHeader(new Zend_Amf_Value_MessageHeader('foo', true, 'bar'))
echo $response;

13.2.4. Objetos tipados

De manera similar a SOAP, AMF permite pasar objetos entre el cliente y el servidor. Esto permite una gran flexibilidad y coherencia entre los dos entornos.

Zend_Amf proporciona tres métodos para mapear objetos de ActionScript y PHP.

  • Primero, puede crear enlaces explícitos a nivel de servidor, utilizando el método setClassMap(). El primer argumento es el nombre de la clase de ActionScript, el segundo el nombre de la clase de PHP con la que se corresponde:

    // Map the ActionScript class 'ContactVO' to the PHP class 'Contact':
    $server->setClassMap('ContactVO', 'Contact');
    
  • Segundo, puede establecer la propiedad pública $_explicitType en su clase de PHP, con el valor representando la clase de ActionScript con la que se corresponde:

    class Contact
    {
        public $_explicitType = 'ContactVO';
    }
    
  • Tercero, de manera similar, puede definir el método público getASClassName() en su clase de PHP; este método debería devolver la clase de ActionScript apropiada:

    class Contact
    {
        public function getASClassName()
        {
            return 'ContactVO';
        }
    }
    

Aunque hemos creado el ContactVO en el servidor, ahora necesitamos crear su clase correspondiente en AS3 para que el objeto del servidor se pueda mapear.

Haga clic derecho en la carpeta src del proyecto Flex y seleccione New -> ActionScript File. Nombre el archivo ContactVO y pulse finish para ver el nuevo archivo. Copie el siguiente código en el archivo para terminar de crear la clase.

package
{
    [Bindable]
    [RemoteClass(alias="ContactVO")]
    public class ContactVO
    {
        public var id:int;
        public var firstname:String;
        public var lastname:String;
        public var email:String;
        public var mobile:String;
        public function ProductVO():void {
        }
    }
}

La clase es sintácticamente equivalente al PHP del mismo nombre. Los nombres de las variables son exactamente los mismos y deben estar en el mismo formato de mayúsculas/minúsculas para funcionar correctamente. Hay dos etiquetas meta únicas de AS3 en esta clase. La primera es bindable, que hace que se dispare un evento de cambio cuando se actualiza. La segunda etiqueta es RemoteClass, que define que esta clase puede tener un objeto remoto mapeado con el nombre de alias, en este caso ContactVO. Es obligatorio que el valor establecido en esta etiqueta sea estrictamente equivalente al de la clase PHP.

[Bindable]
private var myContact:ContactVO;

private function getContactHandler(event:ResultEvent):void {
    myContact = ContactVO(event.result);
}

El siguiente evento de resultado de la llamada al servicio se convierte instantáneamente al ContactVO de Flex. Todo lo que esté vinculado a myContact se actualizará con los datos de ContactVO devueltos.

13.2.5. Recursos

Zend_Amf proporciona herramientas para mapear los tipos de recursos devueltos por las clases de servicio en datos consumibles por ActionScript.

Para manejar un tipo de recurso específico, el usuario necesita crear una clase plugin nombrada según el nombre del recurso, con las palabras en mayúscula inicial y sin espacios (así, el tipo de recurso "mysql result" se convierte en MysqlResult), con algún prefijo, por ejemplo My_MysqlResult. Esta clase debe implementar un método, parse(), que recibe un argumento -- el recurso -- y devuelve el valor que se debe enviar a ActionScript. La clase debe ubicarse en el archivo nombrado según el último componente del nombre, por ejemplo MysqlResult.php.

El directorio que contiene los plugins de manejo de recursos debe registrarse con el cargador de tipos de Zend_Amf:

Zend_Amf_Parse_TypeLoader::addResourceDirectory(
    "My",
    "application/library/resources/My"
);

Para una discusión detallada sobre la carga de plugins, consulte la sección del cargador de plugins.

El directorio por defecto para los recursos de Zend_Amf se registra automáticamente y actualmente contiene manejadores para los recursos "mysql result" y "stream".

// Example class implementing handling resources of type mysql result
class Zend_Amf_Parse_Resource_MysqlResult
{
    /**
     * Parse resource into array
     *
     * @param resource $resource
     * @return array
     */
    public function parse($resource) {
        $result = array();
        while($row = mysql_fetch_assoc($resource)) {
            $result[] = $row;
        }
        return $result;
    }
}

Intentar devolver un tipo de recurso desconocido (es decir, uno para el que no existe ningún plugin manejador) resultará en una excepción.

13.2.6. Conexión al servidor desde Flash

Conectarse a su Zend_Amf_Server desde su proyecto Flash es ligeramente diferente que desde Flex. Sin embargo, una vez establecida la conexión, Flash funciona con Zend_Amf_Server de la misma manera que Flex. El siguiente ejemplo también puede usarse desde un archivo AS3 de Flex. Reutilizaremos la misma configuración de Zend_Amf_Server junto con la clase World para nuestra conexión.

Abra Flash CS y cree un nuevo archivo Flash (ActionScript 3). Nombre el documento ZendExample.fla y guárdelo en una carpeta que utilizará para este ejemplo. Cree un nuevo archivo AS3 en el mismo directorio y llame al archivo Main.as. Tenga ambos archivos abiertos en su editor. Ahora vamos a conectar los dos archivos a través de la clase de documento. Seleccione ZendExample y haga clic en el escenario. Desde el panel de propiedades del escenario cambie la clase de documento a Main. Esto vincula el archivo ActionScript Main.as con la interfaz de usuario en ZendExample.fla. Cuando ejecute el archivo Flash ZendExample, la clase Main.as se ejecutará. A continuación añadiremos ActionScript para realizar la llamada AMF.

Ahora vamos a crear una clase Main para poder enviar los datos al servidor y mostrar el resultado. Copie el siguiente código en su archivo Main.as y luego recorreremos el código para describir el papel de cada elemento.

package {
  import flash.display.MovieClip;
  import flash.events.*;
  import flash.net.NetConnection;
  import flash.net.Responder;

  public class Main extends MovieClip {
    private var gateway:String = "http://example.com/server.php";
    private var connection:NetConnection;
    private var responder:Responder;

    public function Main() {
      responder = new Responder(onResult, onFault);
      connection = new NetConnection;
      connection.connect(gateway);
    }

    public function onComplete( e:Event ):void{
      var params = "Sent to Server";
      connection.call("World.hello", responder, params);
    }

    private function onResult(result:Object):void {
      // Display the returned data
      trace(String(result));
    }
    private function onFault(fault:Object):void {
      trace(String(fault.description));
    }
  }
}

Primero necesitamos importar dos librerías de ActionScript que realizan la mayor parte del trabajo. La primera es NetConnection, que actúa como una tubería bidireccional entre el cliente y el servidor. La segunda es un objeto Responder que maneja los valores de retorno del servidor relacionados con el éxito o fallo de la llamada.

import flash.net.NetConnection;
import flash.net.Responder;

En la clase necesitamos tres variables para representar la NetConnection, el Responder, y la URL de gateway a nuestra instalación de Zend_Amf_Server.

private var gateway:String = "http://example.com/server.php";
private var connection:NetConnection;
private var responder:Responder;

En el constructor de Main creamos un responder y una nueva conexión al endpoint de Zend_Amf_Server. El responder define dos métodos diferentes para manejar la respuesta del servidor. Por simplicidad los he llamado onResult y onFault.

responder = new Responder(onResult, onFault);
connection = new NetConnection;
connection.connect(gateway);

En la función onComplete, que se ejecuta tan pronto como el constructor ha finalizado, enviamos los datos al servidor. Necesitamos añadir una línea más que hace una llamada a la función World->hello de Zend_Amf_Server.

connection.call("World.hello", responder, params);

Cuando creamos la variable responder definimos una función onResult y onFault para manejar la respuesta del servidor. Añadimos esta función para el resultado exitoso del servidor. Un manejador de evento exitoso se ejecuta cada vez que la conexión se maneja correctamente con el servidor.

private function onResult(result:Object):void {
    // Display the returned data
    trace(String(result));
}

La función onFault se llama si hubo una respuesta inválida del servidor. Esto ocurre cuando hay un error en el servidor, la URL al servidor es inválida, el servicio o método remoto no existe, y cualquier otro problema relacionado con la conexión.

private function onFault(fault:Object):void {
    trace(String(fault.description));
}

Añadir el ActionScript para realizar la conexión remota ya está completo. Al ejecutar el archivo ZendExample ahora se realiza una conexión con Zend_Amf. En resumen ha añadido las variables requeridas para abrir una conexión al servidor remoto, definido qué métodos deben usarse cuando su aplicación recibe una respuesta del servidor, y finalmente mostrado los datos devueltos en la salida mediante trace().

13.2.7. Autenticación

Zend_Amf_Server le permite especificar hooks de autenticación y autorización para controlar el acceso a los servicios. Utiliza la infraestructura proporcionada por los componentes Zend_Auth y Zend_Acl.

Para definir la autenticación, el usuario proporciona un adaptador de autenticación que extiende la clase abstracta Zend_Amf_Auth_Abstract. El adaptador debe implementar el método authenticate() al igual que un adaptador de autenticación normal.

El adaptador debe utilizar las propiedades _username y _password de la clase padre Zend_Amf_Auth_Abstract para autenticar. Estos valores son establecidos por el servidor usando el método setCredentials() antes de llamar a authenticate() si las credenciales se reciben en las cabeceras de la petición AMF.

La identidad devuelta por el adaptador debe ser un objeto que contenga la propiedad role para que funcione el control de acceso ACL.

Si el resultado de la autenticación no es exitoso, la petición no se procesa más y se devuelve un mensaje de fallo con las razones del fallo tomadas del resultado.

El adaptador se conecta al servidor usando el método setAuth():

$server->setAuth(new My_Amf_Auth());

El control de acceso se realiza utilizando el objeto Zend_Acl establecido por el método setAcl():

$acl = new Zend_Acl();
createPermissions($acl); // create permission structure
$server->setAcl($acl);

Si el objeto ACL está establecido, y la clase que se está llamando define el método initAcl(), este método se llamará con el objeto ACL como argumento. La clase entonces puede crear reglas ACL adicionales y devolver TRUE, o devolver FALSE si no se requiere control de acceso para esta clase.

Después de que se haya configurado el ACL, el servidor verificará si se permite el acceso con el rol establecido por la autenticación, siendo el recurso el nombre de la clase (o NULL para llamadas a funciones) y el privilegio el nombre de la función. Si no se proporcionó autenticación, entonces si el rol anonymous fue definido, se usará, de lo contrario se denegará el acceso.

if($this->_acl->isAllowed($role, $class, $function)) {
    return true;
} else {
    require_once 'Zend/Amf/Server/Exception.php';
    throw new Zend_Amf_Server_Exception("Access not allowed");
}