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

59
by Joseluis Laso @jl_laso [email protected] Sockets al límite

Upload: joseluis-patrick-laso

Post on 25-May-2015

332 views

Category:

Internet


1 download

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

Page 1: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

by!Joseluis Laso!

!@jl_laso

[email protected]

Sockets al límite

Page 2: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

AgradecimientosAl espacio GeeksHubs.com!Al grupo de Symfony de

Valencia

https://twitter.com/symfony_vlc

Page 3: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 4: Sockets al limite, como explotar los sockets al límite en un proyecto de 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

Page 5: Sockets al limite, como explotar los sockets al límite en un proyecto de 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

Page 6: Sockets al limite, como explotar los sockets al límite en un proyecto de 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ú',!//..

Page 7: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 8: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 9: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 10: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 11: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 12: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Vale, entonces: ¿Qué propones?

Page 13: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 14: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

¿Suena bien?

Page 15: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 16: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Vale, pero…

Con un gestor de traducciones centralizado …

¿Qué pasa con los

archivos de traducciones de nuestros proyectos?

Page 17: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Ahí es donde entra en juego el bundle

que conecta tu proyecto con el

servidor centralizado.

Page 18: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

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

traducciones fuentestienen que ser ignorados.

Veamos cómo…

Page 19: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

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

intercepta las recreaciones de los archivos de

traducciones en cache.

Page 20: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 21: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 22: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Hemos añadido una capa intermedia

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

yml

Page 23: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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;

Page 24: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 25: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Conexión local/remoto

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

Page 26: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 27: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Evoluciónde la conexión

• Ruta convencional.

• ~ Api-REST. • Una petición

por cada key.

Page 28: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 29: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 30: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 31: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 32: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 33: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 34: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 35: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Comando sync desde la consola

Page 36: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

SyncCommand

Page 37: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

SyncCommand (sigue)

Page 38: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

SyncCommand (sigue)

Page 39: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

TAB pide al servidor un socket

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

Page 40: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.comClientSocketService

Page 41: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 42: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Servidor devuelve los datos de conexión (puerto)

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

{"port":"10000"}

Page 43: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 44: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 45: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 46: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 47: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Flujo de datosCliente Servidor

Envío de un bloque

ACK

Page 48: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 49: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 50: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

El servidor utiliza el canal de la misma manera

MiProyectoEnSF2

TAB

TAB: TranslationsApiBundle

/create-socket

{“port”:”10000”}

send => read

read => send

Page 51: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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

Page 52: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

Page 53: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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 …

Page 54: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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)

Page 55: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

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.

Page 56: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

¿Te gusta?

¡Pruébalo!

tradukoj.com !

Es gratis.

El proceso es sencillo.

Es reversible.

Page 57: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

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

Page 58: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

www.tradukoj.com

¿ Preguntas ?

Page 59: Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

GraciasJoseluis Laso!

!@jl_laso

[email protected]!http://www.slideshare.net/JoseluisLaso/sockets-al-limite!

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