sockets al limite, como explotar los sockets al límite en un proyecto de symfony2

Post on 25-May-2015

333 Views

Category:

Internet

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Explicación de como está implementado el bundle translations-apibundle que conecta un proyecto en Symfony2 con el servidor de traducciones centralizado tradukoj.com

TRANSCRIPT

by!Joseluis Laso!

!@jl_laso

jlaso@joseluislaso.es

Sockets al límite

AgradecimientosAl espacio GeeksHubs.com!Al grupo de Symfony de

Valencia

https://twitter.com/symfony_vlc

www.tradukoj.com

Las traducciones en SF2 son gestionadas mediante unos archivos en la carpeta Resources/translations de

cada uno de los distintos bundles, el nombre del archivo tiene esta estructura:

!{catalog}.{language}.{format}!

!Los formatos disponibles actualmente son:!

!!!

Normalmente catalog toma alguno de estos valores:!!!

!aunque puede ser otro cualquiera.

yml xml php

messages validators security

Traducciones en Symfony2

www.tradukoj.com

Los diferentes archivos al final lo que hacen es relacionar una clave con un texto:!

!!!!!!

Primer inconveniente:hay que mantener un sistema en el que se

repiten las claves en varios archivos.

yml!!

header:!        menu:!                  label:    "Menu"

xml!!

<trans-­‐unit  id="1">!        <source>!              header.menu.label!        </source>!        <target>Menu</target>!</trans-­‐unit>

php!!

//  messages.es.php  return  array(!        'header.menu.label'  =>  'Menu',!);

Traducciones en Symfony2

www.tradukoj.com

Las claves se separan en catálogos!(o espacios de nombres) por claridad.!

!En realidad SF2 no trabaja directamente!

con esos archivos.!!

Lo hace con la versión "compilada" que crea en app/cache/{env}/translations/catalogue.{language}.php  

!Esta versión se "compila" en la!

regeneración de la cache.!!!

Veamos cómo es uno de esos archivos...

Traducciones en Symfony2

www.tradukoj.com

app/cache/dev/translations/catalog.es.php

<?php  !use  Symfony\Component\Translation\MessageCatalogue;!!$catalogue  =  new  MessageCatalogue('es',  array  (!    'validators'  =>  !    array  (!        'This  value  should  be  false.'  =>  'Este  valor  debería  ser  falso.',!        'This  value  should  be  true.'  =>  'Este  valor  debería  ser  verdadero.',!//..!    'messages'  =>  !    array  (!        'header.menu.label'  =>  'Menú',!//..

www.tradukoj.com

Traducciones en Symfony2Ahora ya podemos usar esas claves en nuestro

código.!!En un twig:!{{  "header.menu.label"|trans  }}!!En un controlador:!$this-­‐>container!          -­‐>get("translator")!          -­‐>trans("header.menu.label");

www.tradukoj.com

El proyecto: motivación

Una vez aclarado cómo Symfony2 gestiona las traducciones, os voy!

a contar por qué quiero…!

!cambiar este comportamiento.

www.tradukoj.com

La manipulación de los archivos fuentes de las traducciones!

(xml, yml o php) requiere ciertos conocimientos técnicos que no siempre el traductor, revisor o

colaborador posee.

El proyecto: motivación

www.tradukoj.com

Por tanto hay que ir con mil ojos cuando se les pasa algún archivo

de éstos, porque a la vuelta es fácil que SF2 se queje porque haya tabulaciones, no coincidan

las claves, etc, etc, etc..

El proyecto: motivación

www.tradukoj.com

Incluso la edición por parte de técnicos puede producir los mismos conflictos

que el resto del código fuente.!!

Es fácil ponerse de acuerdo para editar, pero hay que acordarse de

hacerlo. Os aseguro que revisar un yml con conflictos es muy divertido!

;<)

El proyecto: motivación

www.tradukoj.com

Vale, entonces: ¿Qué propones?

www.tradukoj.com

Propongo …

Un servidor centralizado para gestionar las traducciones de los desarrollos en

Symfony2.!!

Un sistema con ciertas ventajas: !edición colaborativa, !permisos y roles, !sin necesidad de ningún conocimiento técnico para mantenerlo.

www.tradukoj.com

¿Suena bien?

www.tradukoj.com

El proyectoEmpezó siendo translations.com.es!

Al intuir su posible difusión decidí cambiar a un punto .com!

!Al no quedar libre ninguno en los

principales idiomas me decidí por un idioma menos conocido

(no es éste un proyecto de idiomas ;) ),!!

En esperanto tradukoj(leido TRADUCOI) significa traducciones.

www.tradukoj.com

Vale, pero…

Con un gestor de traducciones centralizado …

¿Qué pasa con los

archivos de traducciones de nuestros proyectos?

www.tradukoj.com

Ahí es donde entra en juego el bundle

que conecta tu proyecto con el

servidor centralizado.

www.tradukoj.com

A partir de la instalación del bundle, los archivos de

traducciones fuentestienen que ser ignorados.

Veamos cómo…

www.tradukoj.com

El bundle instala dentro de su código una clase que

intercepta las recreaciones de los archivos de

traducciones en cache.

www.tradukoj.com

Jlaso/Translations/ApiBundle/Translations/Loader/PdoLoader.php

class  PdoLoader  implements  LoaderInterface,  ResourceInterface  {      //  Esta  es  la  que  se  invoca  para  regenerar  las  traducciones        public  function  load($resource,  $locale,  $domain  =  Translation::DEFAULT_DOMAIN) !      //  Esta  genera  la  sentencia  que  recupera  las  keys  de  la  tabla  local      protected  function  getTranslationsStatement() !      //        public  function  getTranslations($locale,  $criteria,  $hierarchicalArray  =  true) !      //      public  function  registerResources(Translator  $translator) !      //      protected  function  getResourcesStatement() !      //      public  function  isFresh($timestamp) !      //      protected  function  getFreshnessStatement($timestamp)      public  function  getResource()      public  function  getConnection() }

www.tradukoj.com

Jlaso/Translations/ApiBundle/Translations/Loader/PdoLoader.php

class  PdoLoader  implements  LoaderInterface,  ResourceInterface  {                        //.. !        public  function  load($resource,  $locale,                $domain  =  Translation::DEFAULT_DOMAIN)        {                if  ($resource  !==  $this)  {                        return  new  MessageCatalogue($locale);                }                $stmt  =  $this-­‐>getTranslationsStatement();                $stmt-­‐>bindValue(':locale',  $locale,  \PDO::PARAM_STR);                $stmt-­‐>bindValue(':domain',  $domain,  \PDO::PARAM_STR); !                $catalogue  =  new  MessageCatalogue($locale);                while  ($row  =  $stmt-­‐>fetch())  {                        $catalogue-­‐>set($row['key'],  $row['message'],  $domain);                } !                return  $catalogue;        } !        //.. } Se han condensado y/o

eliminado algunas partes por claridad

www.tradukoj.com

Hemos añadido una capa intermedia

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

yml

www.tradukoj.com

/**  *  @ORM\Table(name="jlaso_translations")  *  @UniqueEntity(fields="domain,locale,key")  */ class  Translation {        private  $id;        private  $domain;        private  $locale;        private  $key;        private  $message;        protected  $bundle;        protected  $file;        private  $createdAt;        private  $updatedAt;              //  getters  and  setters  .. }

CREATE  TABLE  `jlaso_translations`  (!    `id`  int(11)  NOT  NULL  AUTO_INCREMENT,!    `domain`  varchar(50)  COLLATE  utf8_unicode_ci  NOT  NULL,!    `locale`  varchar(10)  COLLATE  utf8_unicode_ci  NOT  NULL,!    `key`  varchar(255)  COLLATE  utf8_unicode_ci  NOT  NULL,!    `message`  longtext  COLLATE  utf8_unicode_ci,!    `bundle`  varchar(100)  COLLATE  utf8_unicode_ci  NOT  NULL,!    `file`  varchar(255)  COLLATE  utf8_unicode_ci  NOT  NULL,!    `created_at`  datetime  NOT  NULL,!    `updated_at`  datetime  NOT  NULL,!    PRIMARY  KEY  (`id`)!)  ENGINE=InnoDB  AUTO_INCREMENT=1  DEFAULT  CHARSET=utf8  COLLATE=utf8_unicode_ci;

www.tradukoj.com

Y toda esta introducción es para hablaros de ese conector,

y de cómo he optimizado la ejecución del comando más

pesado:

la sincronización.

www.tradukoj.com

Conexión local/remoto

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

www.tradukoj.com

jlaso/translations-apibundle

• Veamos como ha ido evolucionando la conexión. !

• La primera versión en 45 minutos no había terminado la sincronización. !

• Actualmente en 30 segundos se produce todo el proceso. !

• Los datos de prueba son siempre los mismos.

www.tradukoj.com

Evoluciónde la conexión

• Ruta convencional.

• ~ Api-REST. • Una petición

por cada key.

www.tradukoj.com

Empezando• Un controlador para

cada acción. !

• Una petición por key.

Ventajas:Arquitectura REST conocida. !

Inconvenientes:A mayor número de claves, más

peticiones. Cada una de ellas tiene que negociar de nuevo con el

servidor. !Resultado:

Deplorable, 45 minutos

www.tradukoj.com

Evolución de la conexión

• Ruta convencional.

• ~ Api-REST. • Una petición

por cada key.

• Una petición por cada catálogo e idioma.

www.tradukoj.com

Mejorando

• Se concentran todos los datos de un catálogo en una petición.

Ventajas:Mejora el rendimiento. !

Inconvenientes:problemas con el tamaño de los datos enviados, en ocasiones se

pierden datos. !Resultado:

mejorable, 25 minutos

www.tradukoj.com

Evolución de la conexión• Ruta

convencional. • ~ Api-REST. • Una petición

por cada key.

• Una petición por cada catálogo e idioma.

• Socket • Una petición

por cada catálogo e idioma.

www.tradukoj.com

La evolución• Petición de socket libre. !

• Se evoluciona el modelo API-REST anterior tal cual.

Ventajas:Mejora el rendimiento de manera

brutal. !Inconvenientes:

sigue habiendo problemas con la pérdida de datos. !

Resultado:muy bueno, menos de 5 minutos.

www.tradukoj.com

Evolución de la conexión• Ruta

convencional. • ~ Api-REST. • Una petición

por cada key.

• Una petición por cada catálogo e idioma.

• Socket • Una petición

por cada catálogo e idioma.

• Fraccionando en bloques y comprimido.

www.tradukoj.com

La revolución• Comunicación mediante

bloques de tamaño fijo y comprimiendo los datos enviados por el canal. !

• Reconocimiento de cada paquete recibido.

Inconvenientes:No se controla la pérdida de

paquetes aunque van numerados. !Resultado:

perfecto, medio minuto.

www.tradukoj.com

Comando sync desde la consola

www.tradukoj.com

SyncCommand

www.tradukoj.com

SyncCommand (sigue)

www.tradukoj.com

SyncCommand (sigue)

www.tradukoj.com

TAB pide al servidor un socket

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

www.tradukoj.comClientSocketService

www.tradukoj.com

En el bundle TABpublic  function  createSocket() {        $url  =  $this-­‐>base_url  .  'create-­‐socket/'  .  $this-­‐>project_id;        $postFields  =  json_encode(array(                'key'        =>  $this-­‐>api_key,                'secret'  =>  $this-­‐>api_secret,        ));        $hdl  =  curl_init($url);        curl_setopt($hdl,  CURLOPT_RETURNTRANSFER,  true);        curl_setopt($hdl,  CURLOPT_HTTPHEADER,  array('Accept:  json'));        curl_setopt($hdl,  CURLOPT_POST,  true);        curl_setopt($hdl,  CURLOPT_POSTFIELDS,  $postFields);        curl_setopt($hdl,  CURLINFO_CONTENT_TYPE,  'application_json');                $body  =  curl_exec($hdl);        $info  =  curl_getInfo($hdl);        curl_close($hdl);        $result  =  json_decode($body,  true);                if(!count($result)){                var_dump($info);                die;        }                return  $result; }

Se han condensado y/o eliminado algunas partes

por claridad

www.tradukoj.com

Servidor devuelve los datos de conexión (puerto)

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

{"port":"10000"}

www.tradukoj.com

En el servidor este controlador atiende la ruta !de petición de creación de un socket

@Route("/create-­‐socket/{projectId}") public  function  createSocketAction(…) {        $host  =  php_uname('n');          $found  =  false;          for  ($port  =  self::MIN_PORT;  $port  <  self::MAX_PORT;  $port++)        {                $connection  =  @fsockopen($host,  $port,  $errno,  $errtxt,  500);                if  (is_resource($connection)){                        fclose($connection);                }else{                        $found  =  true;                            break;                }        }        if($found){                $srcDir  =  dirname($this-­‐>get('kernel')-­‐>getRootDir());                $cmd  =  "php  $srcDir  /app/console  ".  self::COMMAND  .                                "  $host  $port  >/dev/null  2>/dev/null  &";                exec($cmd);        } !        return  $this-­‐>resultOk(array('port'  =>  $port)); }

Se han condensado y/o eliminado algunas partes

por claridad

www.tradukoj.com

Ahora la comunicación es por el socket creado y no por http

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

{“port”:”10000”}

send => read

www.tradukoj.com

Formato de los mensajes

block-len : block-num : num-blocks : info

• block-len: indica la longitud del último campo (info)

• block-num: es el número de bloque que se está enviando/recibiendo

• num-blocks: la cantidad total de bloques que se quieren enviar y se van a recibir

• info: el bloque que se está enviando/recibiendo en formato comprimido (lzf) !

Ejemplo de mensaje: 000010:001:001:0123456789

www.tradukoj.com

Campo info en los mensajes

Una vez se ha recompuesto todo el campo info a base de juntar todos los bloques, se descomprime (lzf_decompress) e inmediatamente se decodifica con json_decode !Veamos una petición del índice de catálogos de un proyecto: !{    "auth.key":"key1234",    "auth.secret":"secret1234",    "command":"catalog-­‐index",    "project_id":1 } !Está claro que este campo no necesita ni comprimirse ni enviarse en bloques, pero cuando empiezas a trabajar con las keys y sus traducciones, os aseguro que la cosa se complica por momentos, en términos de longitud.

www.tradukoj.com

Flujo de datosCliente Servidor

Envío de un bloque

ACK

www.tradukoj.com

En el bundle TAB enviamos los mensajes

protected  function  sendMessage($msg,  $compress  =  true) {        $msg  =  lzf_compress($msg);        $len  =  strlen($msg);                $blocks  =  ceil($len  /  self::BLOCK_SIZE);        for($i=0;  $i<$blocks;  $i++){                //  get  Block  to  send                $block  =  substr($msg,  $i  *  self::BLOCK_SIZE,                                              ($i  ==  $blocks-­‐1)  ?            $len  -­‐  ($i-­‐1)  *  self::BLOCK_SIZE  :            self::BLOCK_SIZE);                $prefix  =  sprintf("%06d:%03d:%03d:",  strlen($block),  $i+1,  $blocks);                $aux  =    $prefix  .  $block;                if(false  ===  socket_write($this-­‐>socket,  $aux,  strlen($aux))){                        die('error');                };                //  Wait  for  ACK                do{                        $read  =  socket_read($this-­‐>socket,  10,  PHP_NORMAL_READ);                }while(strpos($read,  self::ACK)  !==  0);        } !        return  true; } Se han condensado y/o

eliminado algunas partes por claridad

www.tradukoj.com

En el bundle TABprotected  function  readSocket() {        $buffer  =  '';        $overload  =  strlen('000000:000:000:');        do{                $buf  =  socket_read($this-­‐>socket,                    $overload  +  self::BLOCK_SIZE,  PHP_BINARY_READ);                if($buf  ===  false){                        echo  socket_strerror(socket_last_error($this-­‐>socket));                        return  -­‐2;                }                list($size,  $block,  $blocks)    =  explode(":",  $buf);                $aux  =  substr($buf,  $overload);                              if($size  ==  strlen($aux)){                        $this-­‐>send(self::ACK);                }else{                        $this-­‐>send(self::NO_ACK);                        die('error  in  size');                }                $buffer  .=  $aux;        }while($block  <  $blocks);                return  lzf_decompress($buffer); }

Se han condensado y/o eliminado algunas partes

por claridad

www.tradukoj.com

El servidor utiliza el canal de la misma manera

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

{“port”:”10000”}

send => read

read => send

www.tradukoj.com

En el servidor discriminamos por el comando solicitado

do{        $buf    =  $this-­‐>readSocket();        $read  =  json_decode($buf,  true);        $command  =  isset($read['command'])  ?  $read['command']  :  '';        //  ..          switch($command){                case  self::CMD_CATALOG_INDEX:  …                case  self::CMD_TRANSDOC_INDEX:  …                case  self::CMD_TRANSDOC_SYNC:  …                                                                          case  self::CMD_TRANSDOC_GET:  …                case  self::CMD_UPLOAD_KEYS:  …                case  self::CMD_DOWNLOAD_KEYS:  …                  case  self::CMD_SHUTDOWN:      $this-­‐>resultOk();                      sleep(1);                      socket_close($this-­‐>msgsock);                      exit;                default:      $this-­‐>exception(sprintf('command  \'%s\'  unknow',  $command));                      break;        } }  while  (true);

Se han condensado y/o eliminado algunas partes

por claridad

www.tradukoj.com

www.tradukoj.com

¿Qué queda por hacer?• Dejar el socket siempre abierto.

!• Control completo de todas las excepciones.

!• Control de envío de paquetes en orden o

repetir si fallo. !

• Tratar archivos xml y php. !

• Subir claves nuevas en bloque. !

• En el editor: mejorar la experiencia de usuario, chat, mailing, edición colaborativa …

www.tradukoj.com

¿Qué más queda por hacer?

• Gestión de usuarios (invitar/añadir). !

• Permitir traducciones abiertas (al estilo de translate.whatsapp.com) en las que colaboran o validan un público más abierto. !

• Poder reservar subdominios para lo anterior o apuntar subdominios externos (estos servicios probablemente serán de pago)

www.tradukoj.com

Agradecimientos

A mi empresa: por hacer de "conejillo de indias" con las traducciones de

!!!

A mis compañeros por prestarse a este

experimento y soportar los inconvenientes iniciales de la

implantación, y sobre todo por aportar las críticas que me han ayudado a

mejorarlo.

www.tradukoj.com

¿Te gusta?

¡Pruébalo!

tradukoj.com !

Es gratis.

El proceso es sencillo.

Es reversible.

www.tradukoj.com

¿Quieres colaborar?Se agradece todo tipo de ayuda: !• desarrollando • testeando • traduciendo • criticando • twitteando • proponiendo • donando • …

www.tradukoj.com

¿ Preguntas ?

GraciasJoseluis Laso!

!@jl_laso

jlaso@joseluislaso.es!http://www.slideshare.net/JoseluisLaso/sockets-al-limite!

http://www.github.com/jlaso/translations-apibundle

top related