SlideShare une entreprise Scribd logo
1  sur  59
Télécharger pour lire hors ligne
by!
Joseluis Laso!
!
@jl_laso
jlaso@joseluislaso.es
Sockets

al límite
Agradecimientos
Al 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	
  SymfonyComponentTranslationMessageCatalogue;!
!
$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 Symfony2
Ahora 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 proyecto
Empezó 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 fuentes

tienen 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
/**
	
  *	
  @ORMTable(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ón

de 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.com
ClientSocketService
www.tradukoj.com
En el bundle TAB
public	
  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 datos
Cliente 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 TAB
protected	
  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 ?
Gracias
Joseluis Laso!
!
@jl_laso
jlaso@joseluislaso.es!
http://www.slideshare.net/JoseluisLaso/sockets-al-limite!
http://www.github.com/jlaso/translations-apibundle

Contenu connexe

Similaire à Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2

PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf
PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdfPHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf
PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf
Raaulroodriguez
 
Script presentacion
Script presentacionScript presentacion
Script presentacion
Vanne Napa
 
Quasi - scripts-linux
Quasi - scripts-linuxQuasi - scripts-linux
Quasi - scripts-linux
degarden
 
Conociendophp 090922184849-phpapp02
Conociendophp 090922184849-phpapp02Conociendophp 090922184849-phpapp02
Conociendophp 090922184849-phpapp02
Oweer Williams
 

Similaire à Sockets al limite, como explotar los sockets al límite en un proyecto de Symfony2 (20)

Symfony 2 CMF
Symfony 2 CMFSymfony 2 CMF
Symfony 2 CMF
 
PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf
PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdfPHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf
PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf PHP.pdf
 
Php!
Php!Php!
Php!
 
Plone en La Jornada
Plone en La JornadaPlone en La Jornada
Plone en La Jornada
 
Introducción al framework Symfony
Introducción al framework SymfonyIntroducción al framework Symfony
Introducción al framework Symfony
 
33 php
33 php33 php
33 php
 
La Caja de Herramientas del Desarrollador Moderno PHPConferenceAR
La Caja de Herramientas del Desarrollador Moderno PHPConferenceARLa Caja de Herramientas del Desarrollador Moderno PHPConferenceAR
La Caja de Herramientas del Desarrollador Moderno PHPConferenceAR
 
Taller de Shell Scripting
Taller de Shell ScriptingTaller de Shell Scripting
Taller de Shell Scripting
 
Script presentacion
Script presentacionScript presentacion
Script presentacion
 
Programacion Orientada a Objetos
Programacion Orientada a ObjetosProgramacion Orientada a Objetos
Programacion Orientada a Objetos
 
The linux shell. Shell Scripting desde 0
The linux shell. Shell Scripting desde 0The linux shell. Shell Scripting desde 0
The linux shell. Shell Scripting desde 0
 
2 poo u1 2 Hola mundo
2 poo u1 2 Hola mundo2 poo u1 2 Hola mundo
2 poo u1 2 Hola mundo
 
Quasi - scripts-linux
Quasi - scripts-linuxQuasi - scripts-linux
Quasi - scripts-linux
 
Symfony en Drupal 8 - DrupalCamp Spain
Symfony en Drupal 8 - DrupalCamp Spain Symfony en Drupal 8 - DrupalCamp Spain
Symfony en Drupal 8 - DrupalCamp Spain
 
Guia programacionwebbasicophp
Guia programacionwebbasicophpGuia programacionwebbasicophp
Guia programacionwebbasicophp
 
Shell script en linux
Shell script en linuxShell script en linux
Shell script en linux
 
Scripts
ScriptsScripts
Scripts
 
Conociendophp 090922184849-phpapp02
Conociendophp 090922184849-phpapp02Conociendophp 090922184849-phpapp02
Conociendophp 090922184849-phpapp02
 
Guiacursophp sql
Guiacursophp sqlGuiacursophp sql
Guiacursophp sql
 
Curso introduccionphp sql
Curso introduccionphp sqlCurso introduccionphp sql
Curso introduccionphp sql
 

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

  • 2. Agradecimientos Al espacio GeeksHubs.com! Al grupo de Symfony de Valencia https://twitter.com/symfony_vlc
  • 3. 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
  • 4. 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
  • 5. 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
  • 6. www.tradukoj.com app/cache/dev/translations/catalog.es.php <?php   ! use  SymfonyComponentTranslationMessageCatalogue;! ! $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ú',! //..
  • 7. www.tradukoj.com Traducciones en Symfony2 Ahora 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");
  • 8. 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.
  • 9. 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
  • 10. 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
  • 11. 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
  • 13. 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.
  • 15. www.tradukoj.com El proyecto Empezó 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.
  • 16. www.tradukoj.com Vale, pero…

 
 Con un gestor de
 traducciones centralizado …
 
 ¿Qué pasa con los
 archivos de traducciones de
 nuestros proyectos?
  • 17. www.tradukoj.com Ahí es donde
 entra en juego el bundle
 que conecta
 tu proyecto con el
 servidor centralizado.
  • 18. www.tradukoj.com A partir de la instalación
 del bundle, los archivos de traducciones fuentes
 tienen que ser ignorados. 
 
 Veamos cómo…
  • 19. www.tradukoj.com El bundle instala dentro
 de su código una clase que intercepta las recreaciones de los archivos de traducciones en cache.
  • 20. 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() }
  • 21. 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
  • 22. www.tradukoj.com Hemos añadido 
 una capa intermedia MiProyectoEnSF2 TAB TAB: TranslationsApiBundle yml
  • 23. www.tradukoj.com /**  *  @ORMTable(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;
  • 24. 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.
  • 26. 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.
  • 27. www.tradukoj.com Evolución
 de la conexión • Ruta convencional. • ~ Api-REST. • Una petición por cada key.
  • 28. 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
  • 29. 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.
  • 30. 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
  • 31. 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.
  • 32. 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.
  • 33. 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.
  • 34. 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.
  • 39. www.tradukoj.com TAB pide al servidor un socket MiProyectoEnSF2 TAB TAB: TranslationsApiBundle /create-socket
  • 41. www.tradukoj.com En el bundle TAB public  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
  • 42. www.tradukoj.com Servidor devuelve los datos de conexión (puerto) MiProyectoEnSF2 TAB TAB: TranslationsApiBundle /create-socket {"port":"10000"}
  • 43. 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
  • 44. 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
  • 45. 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
  • 46. 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.
  • 47. www.tradukoj.com Flujo de datos Cliente Servidor Envío de un bloque ACK
  • 48. 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
  • 49. www.tradukoj.com En el bundle TAB protected  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
  • 50. www.tradukoj.com El servidor utiliza el canal 
 de la misma manera MiProyectoEnSF2 TAB TAB: TranslationsApiBundle /create-socket {“port”:”10000”} send => read read => send
  • 51. 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
  • 53. 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 …
  • 54. 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)
  • 55. 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.
  • 57. www.tradukoj.com ¿Quieres colaborar? Se agradece todo tipo de ayuda: ! • desarrollando • testeando • traduciendo • criticando • twitteando • proponiendo • donando • …