Globalizando Mephisto (2ª Parte)

Bienvenido a la segunda parte de la serie de artículos Globalizando Mephisto donde hablaré de varios temas relacionados con la puesta en producción de un mephisto globalizado.

Os conduciré por los temas de despliegue que aparecen con la configuración que resulta del uso de este plugin.

Raíces y Hosts Virtuales

Como ya mencioné en el articulo anterior el plugin mephisto_i18n mantiene el locale activo de una sesión asumiendo que el tld del host indica la parte del idioma del locale.

e.g.

  example.com => locale base es actualmente activo (e.g. Ingles )
  es.example.com => locale 'es' es actualmente activo (e.g. Castellano )
  fr.example.com => locale 'fr' es actualmente activo (e.g. Francés )

Así que el servidor web necesita múltiples hosts virtuales, uno para cada idioma que la aplicación reconoce.

e.g


  * RAILS_ROOT/public               (El raiz primario del locale base)
  * RAILS_ROOT/public/es             (El raiz secundario del locale 'es')
  * RAILS_ROOT/public/fr             (El raiz secundario del locale 'fr')

En el articulo anterior vimos como los ‘rake tasks’ proporcionados por el plugin, hace que la creación de estos sub raices sea muy sencillo, y a partir de ahora estoy asumiendo ya tienes todo es hecho y mephisto está globalizado y ejecutandose corectamente en modo de desarollo.

Configurando el servidor web

Así que necesitamos configurar el servidor web para manejar multiples raices (hosts virtuales). Necesitas un servido que tiene soporte para name-based virtual hosts. Hoy en día cualquier servidor web decente tendría que tener esta funcionalidad por defecto.

El mio es una taza de nginx

Actualmente, mi servidor preferido es Nginx (ir aquí para ver mas detalles en otros idiomas) así que os mostraré la configuración que utilizco para este servidor.

Desde luego, cualquier servidor web que soporta hosts virtuales nos sirve. p.ej. Apache , Lighttpd

El fichero de configuración de nginx normalmene está ubicado a /usr/local/nginx/conf/nginx.conf

El siguiente nginx.conf asume que hay 4 hosts virtuales diferentes.Uno para cada uno de los locales en,es,ca y fr. También, asume que el Catalán (ca) es el locale base.

Adicionalmente, redirige todas las peticiones para subdominios no reconocidos (incluyendo subdominio del locale base) a www.

p.ej. las peticiones que vienen a algunsubdominio.example.com serán redirigidos a www.example.com. Además, si asumimos que el catalan es el locale base, las peticiones para ca.example.com serán redirigidos a www.example.com

Si no quieres que esta rediricción ocurre no añades esta ultimo host virtual.

Aquí, os muestro el fichero nginx.conf incluyendo los hosts virtuales, pero lo que hago a menudo para producción es poner diferentes directivas para un site especifico en su propio fichero y utilizar una directiva include para incluyirlo.

p.ej. /usr/local/nginx/conf/apps/saimonmoore.net.conf

y solo añade:


include apps/saimonmoore.net.conf;

dentro de la directiva “http”. (Normalmente, lo añado al final). Dentro de cada configuración de una aplicación, también incluyo una llamado a la directiva upstream)


upstream mongrel_saimonmoore.net {
   server 127.0.0.1:8080;
   server 127.0.0.1:8081;
}

Aquí tienes el fichero nginx.conf por completo:


  # user and group to run as
  #user  www-data www-data;
  # number of nginx workers
  worker_processes  2;

  # pid of nginx master process
  pid        /var/log/nginx/nginx.pid;

  # Number of worker connections. 1024 is a good default
  events {
      worker_connections  1024;
  }

  # start the http module where we config http access.
  http {
    # pull in mime-types. You can break out your config
    # into as many include's as you want to make it cleaner
      include       conf/mime.types;
      # set a default type for the rare situation that
      # nothing matches from the mimie-type include
      default_type  application/octet-stream;

      # configure log format
      log_format  main  '$remote_addr - $remote_user [$time_local] $status '
                        '"$request" $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';

      # main access log
      access_log  /var/log/nginx/access.log  main;
      # main error log
      error_log  /var/log/nginx/error.log info;

      # no sendfile on OSX uncomment
      #this if your on linux or bsd
      #sendfile        on;

      # These are good default values.
      tcp_nopush     on;
      keepalive_timeout  65;
      tcp_nodelay        on;

      # this is where you define your mongrel clusters.
      # you need one of these blocks for each cluster
      # and each one needs its own name to refer to it later.
      # To determine how many mongrels are necessary follow:
      # http://mongrel.rubyforge.org/docs/how_many_mongrels.html
      upstream mongrel_example.com {
          server 127.0.0.1:8080;
          server 127.0.0.1:8081;
      }

      # output compression saves bandwidth
      gzip on;
      gzip_comp_level 2;
      gzip_min_length  1100;
      gzip_buffers     4 8k;
      #gzip_proxied any;
      gzip_types       text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

      #server_names_hash_bucket_size 32/64/128
      server_names_hash_bucket_size 64;

      #vhosts for example.com
      #
      #root /var/www/apps/example.com/current/public;
      #Spanish locale: es subdomain
      server {
          listen       80;

          server_name es.example.com;

          root /var/www/apps/example.com/current/public/es;

          access_log  /var/log/nginx/host.access.log  main;
          rewrite_log on;

          if (-f $document_root/maintenance.html){
            rewrite  ^(.*)$  /maintenance.html last;
            break;
          }

          location /index2 {
            rewrite (.*) / permanent;
          }

          location / {
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect false;

            # If the file exists as a static file serve it directly without
            # running all the other rewite tests on it
            if (-f $request_filename) {
              break;
            }

            # check for index.html for directory index
            # if its there on the filesystem then rewite
            # the url to add /index.html to the end of it
            # and then break to send it to the next config rules.
            if (-f $request_filename/index.html) {
              rewrite (.*) $1/index.html break;
            }

            # this is the meat of the rails page caching config
            # it adds .html to the end of the url and then checks
            # the filesystem for that file. If it exists, then we
            # rewite the url to have explicit .html on the end
            # and then send it on its way to the next config rule.
            # if there is no file on the fs then it sets all the
            # necessary headers and proxies to our upstream mongrels
            if (-f $request_filename.html) {
              rewrite (.*) $1.html break;
            }

            if (!-f $request_filename) {
              proxy_pass http://mongrel_example.com;
              break;
            }
          }
      }

      #French locale: fr subdomain
      server {
          listen       80;

          server_name fr.example.com;

          root /var/www/apps/example.com/current/public/fr;

          access_log  /var/log/nginx/host.access.log  main;

          if (-f $document_root/maintenance.html){
            rewrite  ^(.*)$  /maintenance.html last;
            break;
          }

          location /index2 {
            rewrite (.*) / permanent;
          }

          location / {
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect false;

            if (-f $request_filename) {
              break;
            }

            if (-f $request_filename/index.html) {
              rewrite (.*) $1/index.html break;
            }

            if (-f $request_filename.html) {
              rewrite (.*) $1.html break;
            }

            if (!-f $request_filename) {
              proxy_pass http://mongrel_example.com;
              break;
            }
          }
      }

      #English locale: en subdomain
      server {
          listen       80;

          server_name en.example.com;

          root /var/www/apps/example.com/current/public/en;

          access_log  /var/log/nginx/host.access.log  main;

          if (-f $document_root/maintenance.html){
            rewrite  ^(.*)$  /maintenance.html last;
            break;
          }

          location /index2 {
            rewrite (.*) / permanent;
          }

          location / {

            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect false;

            if (-f $request_filename) {
              break;
            }

            if (-f $request_filename/index.html) {
              rewrite (.*) $1/index.html break;
            }

            if (-f $request_filename.html) {
              rewrite (.*) $1.html break;
            }

            if (!-f $request_filename) {
              proxy_pass http://mongrel_example.com;
              break;
            }
          }
      }

      # Catalan locale: www
      server {
          listen       80;

          server_name www.example.com;

          root /var/www/apps/example.com/current/public;

          access_log  /var/log/nginx/host.access.log  main;

          if (-f $document_root/maintenance.html){
            rewrite  ^(.*)$  /maintenance.html last;
            break;
          }

          location /index2 {
            rewrite (.*) / permanent;
          }

          location / {
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect false;

            if (-f $request_filename) {
              break;
            }

            if (-f $request_filename/index.html) {
              rewrite (.*) $1/index.html break;
            }

            if (-f $request_filename.html) {
              rewrite (.*) $1.html break;
            }

            if (!-f $request_filename) {
              proxy_pass http://mongrel_example.com;
              break;
            }
          }
      }

      #Catalan locale: tld
      server {
          listen       80;

          server_name example.com;

          root /var/www/apps/example.com/current/public;

          access_log  /var/log/nginx/host.access.log  main;

          if (-f $document_root/maintenance.html){
            rewrite  ^(.*)$  /maintenance.html last;
            break;
          }

          location /index2 {
            rewrite (.*) / permanent;
          }

          location / {
            proxy_set_header  X-Real-IP  $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect false;

            if (-f $request_filename) {
              break;
            }

            if (-f $request_filename/index.html) {
              rewrite (.*) $1/index.html break;
            }

            if (-f $request_filename.html) {
              rewrite (.*) $1.html break;
            }

            if (!-f $request_filename) {
              proxy_pass http://mongrel_example.com;
              break;
            }
          }
      }

      #Catalan locale: any other subdomain (redirected to www)
      server {
          listen       80;

          server_name ca.example.com *.example.com;
          rewrite  ^(.*) http://www.example.com$1  permanent;
      }
  }

Por cierto, puedes encontrar este fichero dentro del directorio resources/config en el plugin mephisto_i18n.

Aquí perrito…

Una vez que tenemos configurado el servidor web, para llevar el perro de paseo. :) Desafortunadaménte, este perrito será un poco difícil. Bueno basta ya de lenguaje canino y entramos en detalles.

Mongrel es genial. Pero, le gusta servir ficheros.

- Eso no es malo, oigo alguien diciendo.

Para nada, excepto cuando tienes múltiples raices. Tu servidor web tendría que estar configurado para reenviar cualquier petición para contenido no-estático a mongrel. Eso está bien, mongrel recibe la petición, y crea un proceso rails para manejar lo.

Sin embargo, Mongrel, por defecto, también intentará servir contenido estático si recibe una petición para algo que está disponible en RAILS_ROOT/public (la raíz principal de vuestra aplicación).

Esta “funcionalidad” le permite ser utilizado tal cual (sin necesidad de un servidor web aparte) mientras se desarrolla aplicaciones normales y funciona super bien.

Pero imaginete el escenario donde una petición llega para:


http://example.com

  1. El servidor web comprueba la raiz para el locale base (RAILS_ROOT/public), no encuentra ningún index.html y reenvía la petición a mongrel.

Lo voy a representar así:


http://example.com => [nginx] RAILS_ROOT/public/index.html ? servir : reenviar (reenviar)

(se entiende?)

  1. Mongrel también comprueba pero no encuentra ningún index.html en la raiz de la aplicacion (RAILS_ROOT/public – el cual es el unico que conocde mongrel), así que crea un proceso de rails que cachea el resultado como RAILS_ROOT/public/index.html.

Esto se devuelve al servidor web para que lo sirva al navegador del cliente. i.e.


http://example.com/index.html (via nginx) => [mongrel] RAILS_ROOT/public/index.html ? servir : enviar_a_rails (enviar_a_rails)

Vale, así que ahora tenemos cacheado la pagina inciial para el locale base. Vamos a refrescar el navegador y oedir la misma pagina. En breve:


http://example.com => [nginx] RAILS_ROOT/public/index.html ? servir : reenviar (servir)

i.e. el servidor web encontró el fichero RAILS_ROOT/public/index.html en la raiz para el locale base y lo sirvió. “yoopee” para el cacheo de paginas.

Pero ahora nuestro navegante quiere ver la pagina principal en castellano, asñi que hacemos clic sobre el vinculo ‘cambia locale’, en la pagina principal, lo cual envia una petición para:


http://es.example.com

El servidor web lo recibe corectamente y identifica la raiz para el dominio es como RAILS_ROOT/public/es. p.e.j.


http://example.com => [nginx] RAILS_ROOT/public/es/index.html ? servir : reenviar (reenviar)

index.html dentro de la raiz RAILS_ROOT/public/es no se encuentra así que se lo reenvia a mongrel.

Ya que Mongrel es un perrito muy atento, comprueba a ver si puede servir contenido estático.

Como no tiene ni idea para multiples raizes, solamente comprueba el unico raiz que conoce que en este caso es RAILS_ROOT/public. p.ej.


http://es.example.com/index.html (via nginx) => [mongrel] RAILS_ROOT/public/index.html ? servir : reenviar_a_rails (servir)

Mongrel encuentra el fichero index.html creado para el locale base, y aunque muy atento, no es un perrito moy listo así que sirve eso en vez de, como desearíamos; crear un proceso rails:


http://es.example.com/index.html => RAILS_ROOT/public/es/index.html

Lo que ha pasado aquí es que el cliente ha pedido:


http://es.example.com/index.html (RAILS_ROOT/public/es/index.html)

pero en realidad fue servido:


http://example.com/index.html (RAILS_ROOT/public/index.html)

Sientate!

Esto es un poco problemático :) y estoy seguro que hay múltiples maneras de solucionarlo (p.ej. utilizar fcgi/scgi) pero si quieres continuar de utilizar mongrel, entonces necesitas poder decirle a mongrel que no quieres que vuelva a servir contenido estático en la vida.

De esta manera, únicamente el servidor web es responsable de servir contenido estatico y la tarea de mongrel es solamente crear el contenido estático.

Por lo que yo se, mongrel no tiene esta habilidad ni tiene soporte para hosts virtuales.

Lo ideal sería que tenga soporte para hosts virtuales pero como era mucho mas sencillo, creé mi propia versión haqueada de mongrel (y del gem mongrel_cluster) para añadir una opción de configuración extra (dont_serve_static default: false).

Cuando esto está puesto a true, mongrel crearé procesos rails para cada petición que recibe, lo cual soluciona nuestro problema.

Puedes encontrar estas versiones haqueadas de mongrel aquí

Para instalar:


#matarl mongrel si está ejecutandose
sudo /etc/init.d/mongrel_cluster stop
sudo gem uninstall mongrel mongrel_cluster
cd /tmp
wget http://www.webtypes.com/projects/files/basecamp/webtypes/webtypesthebusiness/mongrel_with_dont_serve_static.tar.gz
tar xvfz mongrel_with_dont_serve_static.tar.gz
cd mongrel_with_dont_serve_static
rake package
sudo gem install pkg/mongrel-1.0.1.gem
#Successfully installed mongrel, version 1.0.1

lo mismo para mongrel_cluster :


cd projects/mongrel_cluster/
rake package
sudo gem install pkg/mongrel_cluster-0.2.1.gem
#Successfully installed mongrel_cluster, version 0.2.1

Una vez instalado, solo hay que modificar tu fichero mongrel_cluster.yml a algo parecido a:


---
user: saimon
cwd: /var/www/apps/saimonmoore.net/current
port: "8080" 
environment: production
group: deploy
address: 127.0.0.1
pid_file: log/mongrel.pid
servers: 2
dont_serve_static: true

Toma nota del dont_serve_static: true. Esa es la opción que dice a mongrel de solamente hacer lo que le decimos y crear procesos rails.

Conclusión

Bueno pues, entre esto dos articulos, tendrás de poder globalizar mephisto facilmente. Si tienes alguna duda, pregunta, sugerencia o has encontrado alguna error entonces por favor deja un mensaje aquí.

Espero que esto proporciona un solucion i18n para la gente que utilizan Mephisto.

Quiza, podemos ofrecer versiones globalizadas de los temas (themes) principales de mephisto?

Quieres una instancia multi site de mephisto globalizado? Lea la 3ª Parte

Saimon Moore (también disponible en Griego, Ingles y Catalán :)

6 comentarios sobre “Globalizando Mephisto (2ª Parte)”

  1. sol

    Great articles!
    Any plans to get this work with a multisite mephisto setup?

  2. Saimon Moore

    Yes, I do. We’re planning on converting this site itself to a multisite mephisto instance so stay tuned.

  3. sol

    great news, thanks a lot!

  4. Lens

    Great, thank you.

  5. izmir halı yıkama,bornova halı yıka

    thank you web sites

  6. sepetli platform

    Thanks for this article and most generaly for this very amazing website :) this information most important for me.

Deja tu comentario