Quantcast
Channel: Planeta Código
Viewing all 2713 articles
Browse latest View live

Poesía Binaria: Cómo crear milters en Python y configurar Postfix para filtrar correo

$
0
0

Milters en Postfix
Una de las herramientas fundamentales a la hora de montar nuestro propio servidor de correo es la implementación de filtros (Mail Filters) para seleccionar de forma eficiente el correo que vamos a procesar. Entre otras cosas, podremos:

  • Hacer más eficiente nuestro servicio ya que enviaremos solo los correos que cumplan unas determinadas reglas (eliminan SPAM, virus y otros tipos de cosas que no queremos recibir.
  • Modificar ciertos mensajes para automatizar procesos en nuestra compañía, por ejemplo, añadir destinatarios en copia automáticamente, añadir una cabecera adicional al correo, incluir un sistema de seguimiento al mensaje…
  • Monitorizar el número de correos recibidos y enviados por el servidor. Con fines estadísticos y, por supuesto para mejorar el servicio que estamos prestando.
  • Restringir el envío de correo a determinados hosts/IPs/dominios/usuarios. Por ejemplo, teniendo un sistema de créditos, o impidiendo que se envíen de forma automatizada muchos mensajes a través de nuestro servidor como una protección extra contra spammers u ordenadores zombies (incluso podríamos avisar a alguien si esto pasa).
  • Implementación de listas blancas, negras o grises.
  • Espiar mensajes. Vale, como administrador de sistemas siempre me dicen que si espío los mensajes de la gente y siempre gasto la broma de que tengo un programa que me avisa cuando hablan de mí. Aunque no lo tengo, porque me da pereza, con este tipo de filtros podría hacerlo.

Por otro lado, cuando montamos un nuevo sistema debemos pensar en la futura escalabilidad del mismo. Es decir, vamos a pensar en cuando nuestro sistema sea grande. Los servicios tanto de entrega, recepción de correo, incluso filtros podrán estar en máquinas separadas. Así que la forma de atacar a estos sistemas será haciendo conexiones de red. Puede que para un sistema muy pequeño perdamos algo de rendimiento, ya que el establecimiento de la conexión y la negociación de la misma puede tardar un tiempo que no tardaría la ejecución de un programa local, pero que hacen posible la separación de los servicios, incluso podríamos colocar los filtros detrás de un balanceador de carga y destinar varias máquinas a éstos. De todas formas, para este ejemplo, vamos a utilizar sockets unix por lo que, en local, será muy rápido y no será complicado hacer que en lugar de un socket Unix estemos utilizando un host/puerto.

Configuración de Postfix

Postfix es capaz de ejecutar muchos milters cada vez que viene un mensaje. Estos milters, con respecto a cuándo se ejecutan antes de encolar los mensajes y pueden ser:

  • tipo smtpd: Los que se ejecutan para los mensajes que vienen desde conexiones SMTP. Pueden ser de usuarios que se conectan a nuestro servidor para enviar mensajes o de otros servidores que se conectan a nosotros para entregar mensajes.
  • tipo no-smtpd: Serán mayormente los que vengan de manera local. Mensajes generados en el propio servidor o alguna de las aplicaciones que hay en él, como sendmail.

Solo tenemos que ir al fichero /etc/postfix/main.cf y, por ejemplo al final, añadir:

smtpd_milters = milter1, milter2, {milter3, …},…
milter_default_action = accept

De esta forma, por defecto, si algún milter no se ejecuta, por defecto aceptamos el mensaje. Por ejemplo, si estamos utilizando un milter de monitorización, si el milter falla, que por lo menos se entregue el mensaje. Si es un milter de filtro de correo, debemos ver qué es más importante, que cuando el milter falle rechace todo (reject) o si deberíamos entregarlo. Otras acciones que pueden suceder cuando el milter falle son tempfail (fallo temporal) o quarantine (cuarentena).

Como hemos visto, podemos separar una serie de filtros por comas, incluso, a veces, ponemos configuración del milter entre llaves. Eso sí, si solo tenemos uno, no hacen falta comas, y si la configuración es sencilla, llaves tampoco. Vamos a ver un poco el por qué de todo esto.

En principio, con respecto a cómo se ejecutan, podemos tener otros dos tipos de milters:

  • unix:/ruta/al/socket : serán los milters asociados a un socket Unix accesible a través de una ruta local.
  • inet:host:port : serán los milters asociados a un host y un puerto. Es decir, los que podemos colocar en una máquina diferente.

También hemos visto que los milters podemos ponerlos entre llaves, esto es cuando su configuración es más compleja que solo decir dónde tiene que conectar, por ejemplo podemos controlar:

  • connection_timeout : Cuánto tiempo va a esperar Postfix a la conexión con el milter. Ya que es un servicio que puede ser externo, la conexión puede no ser inmediata. Por defecto son 30s.
  • command_timeout : Cuánto tiempo va a esperar Postfix a que el milter responda tras el envío de un comando. Nuestro filtro tiene tiempo para realizar operaciones, pero tampoco se puede eternizar porque constantemente están llegando mensajes y el proceso no puede parar. Son 30s por defecto.
  • content_timeout : Cuánto tiempo va a esperar Postfix tras el envío de contenidos del mensaje para una respuesta por parte del milter. Por defecto son 300s.
  • default_action : Podemos especificar la acción por defecto para cada milter. Esta puede ser accept, reject, tempfail, quarantine
  • Podemos consultar una lista completa de configuraciones aquí.

Así, un ejemplo de configuración de milter puede ser:

smtpd_milters = unix:/var/run/milters/monitor, {inet:blacklist.mydomain.com:6234, connection_timeout=5x, default_action=tempfail, command_timeout=1s}
milter_default_action = accept

Milters en Python

Aunque podemos crear nuestros milters en C o C++, es más, podemos encontrar muchos ejemplos que utilizan libmilter y funcionan muy bien. Hoy le toca el turno a Python, de hecho la biblioteca de Python para trabajar con milters se apoya en la biblioteca de C. Aunque no tendrá tanto rendimiento como uno programado en C, en el hipotético caso en el que el filtro lo hagamos optimizado en cada lenguaje, Python nos hace mucho más fácil el mantenimiento y el desarrollo, pudiendo crecer muy rápidamente. Lo primero será preparar las dependencias. Podemos instalarlas con pip:

sudo pip install pymilter

Aunque, dependiendo de nuestro filtro, podremos tener muchas más dependencias. Ya nuestra imaginación no tiene límites.

Creando el milter en Python

Vamos a crear un milter de ejemplo en Python. En este ejemplo, solo vamos a permitir mensajes que se envíen hacia las direcciones de correo que figurarán en un archivo llamado whitelist. Por lo que nuestro servidor no podrá mandar correo a cualquiera. El contenido de whitelist puede ser el siguiente:

mi@correo.com
yo@dominio.com
otromail@otrodominio.com

El milter está basado en este. Ahí va el código:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# -*- coding: utf-8 -*-
# Milter para filtrar los mails que vienen de direcciones entrantes

import Milter
importStringIO
importtime
importemail
importsys
fromsocketimport AF_INET, AF_INET6
from Milter.utilsimport parse_addr

ifTrue:
  from multiprocessing import Process as Thread,Queue
else:
  fromthreadingimport Thread
  fromQueueimportQueue

logq =Queue(maxsize=4)

class myMilter(Milter.Base):

  def__init__(self):  # Se crea una instancia con cada conexión entrante
    self.id= Milter.uniqueID()

  @Milter.noreply
  def connect(self, IPname, family, hostaddr):
    self.IP= hostaddr[0]
    self.port= hostaddr[1]
    if family == AF_INET6:
      self.flow= hostaddr[2]
      self.scope= hostaddr[3]
    else:
      self.flow=None
      self.scope=None
    self.IPname= IPname  
    self.H=None
    self.fp=None
    self.receiver=self.getsymval('j')
    self.log("connect from %s at %s" % (IPname, hostaddr))
   
    return Milter.CONTINUE


  def hello(self, heloname):
    # Cuando entra un nuevo mail vemos esto.
    self.log("Recibo HELO")
    self.H= heloname
    self.log("HELO %s" % heloname)
     
    return Milter.CONTINUE

  def envfrom(self, mailfrom, *str):
    # Cuando se indica un remitente entra por aquí. Podemos elegir si continuar o no
    self.F= mailfrom
    self.R=[]
    self.fromparms= Milter.dictfromlist(str)
    self.user=self.getsymval('{auth_authen}')
    self.log("mail from:", mailfrom, *str)

    self.fp=StringIO.StringIO()
    self.canon_from='@'.join(parse_addr(mailfrom))
    self.fp.write('From %s %s\n' % (self.canon_from,time.ctime()))
    return Milter.CONTINUE


  def envrcpt(self, to, *str):
    # Cuando recibimos un destinatario, entra por aquí
    filename ='whitelist'
    # Obtenemos el listado de correos aceptados
    addresses =tuple(el.strip()for el inopen(filename,'r')if el.strip())

    tomail ='@'.join(parse_addr(to))

    ifnotany(tomail.lower()in l for l in addresses):
       return Milter.REJECT

    rcptinfo = to,Milter.dictfromlist(str)
    self.R.append(rcptinfo)
   
    return Milter.CONTINUE


  @Milter.noreply
  def header(self, name, hval):
    # Cuando recibimos un encabezado entramos por aquí
    self.fp.write("%s: %s\n" % (name,hval))
    return Milter.CONTINUE

  @Milter.noreply
  def eoh(self):
    # Cuando terminamos los encabezados entramos a esta función
    self.fp.write("\n")                       
    return Milter.CONTINUE

  @Milter.noreply
  def body(self,chunk):
    # Cuando se recibe cada uno de los trozos del cuerpo del mensaje entramos a este punto
    self.fp.write(chunk)
    return Milter.CONTINUE

  def eom(self):
    # Cuando terminamos de recibir el mensaje entramos a esta función
    self.fp.seek(0)
    msg =email.message_from_file(self.fp)
    return Milter.ACCEPT

  def close(self):
    # Cierre de conexión y limpieza de recursos. Si se llama a abort() se llamará luego a close()
    # automáticamente
    return Milter.CONTINUE

  def abort(self):
    # El cliente se ha desconectado de forma prematura. ¿Un fallo en postfix o al enviar el mensaje?
    return Milter.CONTINUE

  def log(self,*msg):
    # Logea algo
    logq.put((msg,self.id,time.time()))

def background():
  whileTrue:
    t = logq.get()
    ifnot t: break
    msg,id,ts = t
    print"%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),

    for i in msg: print i,
    print

## ===
   
def main():
  bt = Thread(target=background)
  bt.start()
  socketname ="/var/run/milters/whitelistmilter"
  timeout =600

  Milter.factory= myMilter
  flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS
  flags += Milter.ADDRCPT
  flags += Milter.DELRCPT
  # Le decimos al Milter los puntos que "interceptaremos"
  Milter.set_flags(flags)     
  print"%s milter startup" % time.strftime('%Y%b%d %H:%M:%S')
  sys.stdout.flush()
  Milter.runmilter("pythonfilter",socketname,timeout)
  logq.put(None)
  bt.join()
  print"%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S')

if __name__ =="__main__":
  main()

Probando nuestro milter

Para probar esto, desde un terminal, podemos ejecutar nuestro script python de nuestro milter, reiniciar Postfix e intentar enviar un correo a través del servidor SMTP elegido. Si queremos hacerlo todo desde terminal, podemos probar utilizar este script para enviar correos desde terminal.

Aunque todo esto podemos hacerlo, si la configuración nos lo permite a pelo. Y puede que en el futuro haga un post dedicaco a esto. Pero por ahora nos apañaremos con este apartado. Nuestro servidor de correo requeire autentificación, de hecho para las pruebas he utilizado un Postfix configurado como relay, con toda la configuración del anterior post. incluyendo el nombre de usuario y contraseña para poder acceder al servidor de correo. Para ello, y como vamos a utilizar telnet, vamos a prepararnos en un terminal la siguiente información:

echo -n ‘nombre_de_usuario’ | openssl base64
bm9tYnJlX2RlX3VzdWFyaW8=
echo -n ‘contraseña’ | openssl base64
Y29udHJhc2XDsWE=

Como estamos utilizando en el servidor el modo AUTH LOGIN, tendremos que escribir el usuario y la contraseña codificados en base64. Los textos codificados, podemos dejarlos visibles para copiarlos y pegarlos cuando convenga. Ahora, accederemos al servidor de correo (el puerto será el 25, y si lo tenemos instalado en un VPS, tendremos que asegurarnos de que nuestro proveedor tiene abierto el puerto 25. A veces necesitamos enviar un mensaje al proveedor para que lo activen). ¡Fijaos, la IP es digna de CSI!

telnet mi.servidor.de.correo.com 25
Trying 258.259.260.261…
Connected to 258.259.260.261.
Escape character is ‘^]’.
220 mi.servidor.de.correo.com ESMTP Postfix (Debian/GNU)
EHLO dominio
250-mi.servidor.de.correo.com
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH LOGIN PLAIN CRAM-MD5 DIGEST-MD5 NTLM
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 SMTPUTF8
AUTH LOGIN
334 VXNlcm5hbWU6
bm9tYnJlX2RlX3VzdWFyaW8=
334 UGFzc3dvcmQ6
Y29udHJhc2XDsWE=
235 2.7.0 Authentication successful
MAIL FROM: probando@dominio.com
250 2.1.0 Ok
RCPT TO: direccion@noestaenlalista.com
550 5.7.1 Command rejected
RCPT TO: direccion@SIestaenlalista.com
250 2.1.5 Ok
DATA
354 End data with .
Escribo un correo solo con cuerpo
Sin asunto
.
250 2.0.0 Ok: queued as 6110CA0A43

Como vemos, cuando introducimos una dirección de correo que no está en la lista, nos aparece el comando como rechazado (Command rejected), mientras que cuando la dirección está en la lista, nos devuelve Ok. Si queremos, podemos mirar el log en la ventana donde estamos ejecutando el script para ver el resultado y observar que efectivamente están llegando los comandos a nuestro milter.

Creación del servicio

Como queremos que nuestro milter siempre esté en ejecución, debemos crear un servicio. Por ejemplo, para systemd podemos crear el siguiente archivo: whitelistmilter.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=Milter startup
After=networking.target

[Service]
WorkingDirectory=/home/user/milters/
Type=simple
ExecStart=/usr/bin/python /home/user/milters/whitelistmilter.py
KillSignal=SIGTERM
User=postfix
Group=postfix

[Install]
WantedBy=multi-user.target

Luego podemos crear un enlace a /etc/systemd/system y arrancar el servicio:

sudo ln -s $(pwd)/whitelistmilter.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable whitelistmilter
sudo systemctl start whitelistmilter

En este punto ya podemos reiniciar Postfix para empezar a utilizar el filtro:

sudo systemctl restart postfix.service

Problemas que pueden surgir

Postfix chroot y acceso a sockets Unix

Uno de los problemas más comunes que podemos tener es que Postfix se ejecuta en un chroot o jaula. Por lo que, cuando especificamos la ruta de un socket Unix, la ruta esté correcta y todo deba funcionar perfectamente. En los ficheros de log veremos que la ruta no se encuentra. Esto se debe a que el unix socket no está dentro del chroot de postfix y efectivamete no se verá.

Una posible solución a esto es meter nuestro sockets unix de los milters en el directorio /var/run/milters y luego hacer:

sudo mkdir /var/spool/postfix/var/run
sudo mount --bind /var/run/milters /var/spool/postfix/var/run/milters

Con esto, creamos un directorio var/run dentro de la ruta de la jaula de postfix y luego montamos el directorio local /var/run/milters dentro de la jaula. De esta forma, postfix ya tendrá acceso a los milters bajo su ruta absoluta /var/run/milters (recordemos que para Postfix, el directorio raíz / es el directorio /var/spool/postfix para el resto de los mortales.

Foto principal: unsplash-logoandrew welch

The post Cómo crear milters en Python y configurar Postfix para filtrar correo appeared first on Poesía Binaria.


Variable not found: Enlaces interesantes 321

$
0
0
Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada, muy condicionados por las jugosas novedades presentadas en el pasado Build 2018. Espero que os resulten interesantes. :-)

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

    Data

    HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publiado en Variable not found.

    Variable not found: Capturar la pantalla con Windows 10

    $
    0
    0
    Enlaces interesantesHago capturas de pantalla muy frecuentemente, a diario diría yo, tanto tanto para crear entradas del blog como durante mi actividad profesional. Y siempre sigo la misma secuencia: pulso la tecla ImprPant, o a lo sumo Alt+ImprPant si sólo necesito el contenido de una ventana; tras ello, acudo a una herramienta de edición como Paint.net, pego la imagen desde el portapapeles y desde ahí recorto la sección que me interesa.

    Pues sí, todo muy manual, como en los viejos tiempos ;) Creo que estas funciones deben andar por ahí desde los tiempos de Windows 95 o quizás antes, así que simplemente sigo haciendo lo mismo que he hecho toda la vida.

    Pero como ha ocurrido en otras ocasiones, creo que debo ser de los últimos en saber que Windows incluye desde hace ya bastante tiempo herramientas más sofisticadas para conseguir capturas de pantalla de forma sencilla, así que aprovecharemos este post para hacer una pequeña puesta al día.

    Windows + Impr Pant

    Esta combinación de teclas introduce en el portapapeles una captura de todas las pantallas activas (es decir, si tenéis varios monitores se incluirá el contenido de todos ellos), y al mismo tiempo se almacenará de forma automática en un archivo con el nombre “Captura de pantalla (X).png” en la carpeta “Este equipo/Imágenes/Capturas de pantalla“.

    Al pulsar Windows e Impr Pant veremos un destello rápido y automáticamente tendremos el resultado en la carpeta y ya desde ahí podremos compartirla, editarla o hacer lo que queramos con el archivo:


    Windows + Mayúsculas + S

    Este ha sido mi gran descubrimiento porque me ahorra bastante tiempo en la mayoría de ocasiones. Pulsando esta combinación de teclas, la pantalla se congelará y, utilizando el ratón, podremos seleccionar el área de la pantalla que nos interese desde cualquier monitor que estemos usando.

    Tras finalizar la selección, el contenido pasará automáticamente al portapapeles.


    Herramienta de recortes

    Si sois de los que en su momento no habéis practicado con Street Fighter lo suficiente como para tener soltura en la ejecución exitosa de combinaciones de teclas, no os preocupéis, porque Microsoft también ha pensado en vosotros ;)

    Con la aplicación Recortes (snippingtool.exe), disponible creo que desde Windows 7, tenemos herramientas bastante potentes para realizar capturas de pantalla o porciones de ésta e incluso insertarle pequeñas anotaciones. Para lanzar la aplicación, basta con buscarla en el menú de inicio, carpeta “Accesorios de Windows”, o bien buscar directamente el texto “recortes”.

    Herramienta recortes

    Desde sus menús podemos seleccionar el tipo de captura que queremos realizar (pantalla completa, ventana, área rectangular o área libre), aplazar la captura varios segundos (como en las cámaras de fotos) y lanzar una nueva captura. Una vez realizada, aparecerá un pequeño editor con el que podremos utilizar un lápiz o un marcador para resaltar alguna zona específica:

    Editor de recortes

    Pero no se vayan todavía, aún hay más… en el horizonte

    Pues sí, aún tendremos próximamente más mejoras en este sentido. Según han anunciado en el blog oficial, la próxima revisión de Windows, ya disponible para insiders, permitirá obtener capturas aún más rápidamente mostrando una pequeña barra de herramientas de recortado al pulsar las teclas Windows+Mays+H. También, tras pasar la imagen al portapapeles y salvarla como archivo, aparecerán notificaciones que permitirán compartirla rápidamente o saltar a la herramienta de anotaciones.

    New snipping experience

    ¡Y aquí nos quedamos! Espero que estos trucos puedan ayudar a algún despistado más que, como el que os habla, faltó a clase el día que explicaron cómo capturar pantallas y realizar recortes de forma eficaz en versiones actuales de Windows ;)

    Publicado en Variable not found.

    Navegapolis: Los 10 valores de Buffer

    $
    0
    0

    nuestros valores

    1. Siempre elegimos felicidad y positivismo

    • Hacemos lo todo posible para abordar las cosas de una manera positiva y optimista.
    • Evitamos criticar o condenar a miembros de nuestro equipo o usuarios.
    • Evitamos quejarnos.
    • Dejamos que la otra persona salve su prestigio, incluso si está claramente equivocada.
    • Somos reflexivos al momento de dar nuestra apreciación genuina.

    2. Somos transparentes por defecto

    • Nos enorgullece tener la oportunidad de compartir nuestras creencias, fracasos, fortalezas y decisiones.
    • Usamos la transparencia como herramienta para ayudar al resto.
    • Siempre exponemos nuestros pensamientos de forma inmediata y con honestidad.
    • Compartimos nuestra opinión de forma temprana en la toma de decisiones para evitar “grandes revelaciones”.

    3. Nos enfocamos en la mejora personal

    • Somos conscientes de nuestro nivel actual de productividad y felicidad; y hacemos cambios continuos para crecer.
    • Tenemos la mayor expectativa de nosotros mismos, mayor que la que cualquier otra persona podría tener de nosotros.
    • De forma regular y reflexivamente, hacemos cosas que nos hacen sentir incómodos.
    • Practicamos actividades y desarrollamos hábitos que mejorarán nuestra mente y cuerpo.

    4. Hacemos las cosas “sin ego”

    • No nos aferramos de forma personal a ninguna idea.
    • Nos adaptamos y estamos de acuerdo con tomar decisiones y trabajar en situaciones de incertidumbre.
    • Nos inclinamos hacia la acción: Hacerlo bien el día de hoy, es mejor que perfecto la siguiente semana.
    • Practicamos la humildad.
    • Publicamos nuestro código/trabajo el momento en el que es mejor que lo que está actualmente en el sitio web.

    5. Escuchamos primero, luego escuchamos más

    • Buscamos primero entender, para luego ser comprendidos.
    • Nos enfocamos en escuchar antes que en responder.
    • Tomamos la aproximación de que todo es una hipótesis y que podríamos estar equivocados.
    • Preferimos sugerir en vez de ordenar, al reemplazar frases como “ciertamente”, “sin duda”, etc. con “tal vez”, “creo que”, “mi intuición en este momento”.

    Picando Código: Call Of Data 2018: El mayor evento de España en torno al Data Science con perspectiva de género

    $
    0
    0

    El 2 y 3 de junio se celebra en Madrid la segunda edición del Call of Data, un evento que tiene el objetivo de impulsar el Data Science en España y de incluir a las mujeres en la programación. Comparto con ustedes la información de este genial evento:

    Call Of Data - 2018

    El primer día del Call Of Data contará con diez charlas: seis ponencias correrán a cargo de las expertas invitadas Carmen Reina, Hannah Frick, Mariluz Congosto, Miriam Pena, Celeste Durán y Nerea Luis. Las cuatro restantes serán charlas cortas escogidas a través de un Call for Papers anónimo, y pronto se anunciarán las elegidas.

    En la segunda jornada se celebrará un hackatón de datos o datatón: una competición de Data Science en la que alrededor de 20 equipos, de un máximo de 4 personas y compuestos por mujeres en al menos un 50%, analizarán un dataset solidario, que se dará a conocer el mismo día, para presentar un proyecto que aporte valor.

    El dataset se entregará ya limpio y los equipos tendrán 8 horas para explorarlo, cruzarlo con otros datasets que sean públicos, y proponer soluciones basadas en ellos. Tendrán libertad para resolver el problema que quieran, y podrán trabajar en cualquier lenguaje de programación siempre que sea de código libre (Python, R, Julia…). Las inscripciones de equipos siguen abiertas; además, desde la organización se ofrecen a ayudar a formar equipo a quien quiera participar y no tenga compañeros o compañeras suficientes.

    Los portavoces de los equipos deberán presentar su propuesta en 4 minutos, que será evaluada por un jurado de expertas en análisis de datos y generación de negocio, compuesto por Gema Parreño, Ibon Tolosana, Irene Rodríguez, Jorge Barroso y Aurora Barrero, a través de los insights que provea dicho análisis. Al final del Call Of Data se anunciará el equipo ganador, que recibirá un jugoso premio.

    Las principales novedades con respecto a la edición del año pasado son la ampliación del evento a dos días, lo que permitirá más ponencias y más tiempo para el datatón, y el Call for Papers para impulsar la participación de más personas. Pero también este año se pretende conseguir más participantes, por lo que se celebrará en un espacio más grande como es La Nave, se ha habilitado una versión en inglés de la página del evento y se ha puesto a disposición de los asistentes un descuento del 30% en billetes de Renfe para facilitar el acceso a Madrid desde otros puntos de España.

    Call Of Data está organizado por R-Ladies Madrid, que ha contado con la ayuda de otras comunidades como PyLadies Madrid, Big Data Madrid, Codenares y Open Source Weekends y la empresa Space Nomads en esta edición. Además, cuentan con Datatons, el Instituto de Empresa (IE), Altran, KSchool, Decathlon, Metroscopia, Neo4j, Máxima Formación y T3chfest como patrocinadores para hacer posible este evento.

    Call Of Data quiere impulsar el trabajo de las mujeres en las áreas STEM, sectores que presentan diferentes problemas de acceso para ellas, como consecuencia de barreras culturales y sociales, incluyendo estereotipos y prejuicios de género.

    Página oficial del evento: http://callofdata.info

    Fotos de la edición de 2017: http://callofdata.info/2017/#photos

    Hashtags oficiales: #CallOfData#CallOfData2018

    Picando Código: Emacs y personas: un post “personal”

    $
    0
    0

    EmacsHace poco vi un artículo compartido en reddit sobre “Usuarios famosos de Emacs (que no son famosos por usar Emacs)”. Entre ellos se encuentran Martin Fowler, Yukihiro “Matz” Matsumoto (creador de Ruby 🙌), Guido van Rossum (creador de Python), Julian Assange, Linus Torvalds, Donal Knuth, Eric Raymond, Richard Stallman, y más.

    En los comentarios un usuario menciona a su profesor de sistemas operativos como alguien que debería estar en la lista. Esto me hizo pensar en personas con las que compartí el uso de Emacs que fueron importantes en mi experiencia. Generalmente agregan más valor que los “famosos” que conozcamos, y alimentan la motivación de seguir aprendiendo y usando Emacs, a pesar de no ser el editor de texto más popular.

    En mi caso la persona que identifico como que “debería estar en esa lista” es Jim Weirich. Siempre es bueno recordarlo como un buen ejemplo a seguir. Y siempre me alegro al ver que es recordado de alguna forma u otra en cada conferencia Ruby que voy. En mi post de 2015: Emacs, mi editor de texto, comentaba:

    (…) recuerdo con mucho cariño y como uno de los puntos altos de mi carrera una vez que tuve la oportunidad de trabajar sobre Emacs con Jim Weirich.

    Jim estaba visitando las oficinas de Neo en Montevideo, y se puso a disposición para hacer pair programming con quien quisiera. No estaba trabajando en un proyecto muy interesante en ese entonces, pero sabía que Jim era usuario de Emacs desde hacía mucho tiempo. Así que fui con la propuesta de hacer pair programming con nuestras configuraciones de Emacs. En una hora o dos con Jim aprendí muchísimo. Intercambiamos trucos y bloques de código y en ese momento me llené de orgullo al ver que Jim agregaba código que yo le había pasado a su configuración de Emacs.

    Es una de las memorias que me quedó grabada muy fuertemente, y recuerdo exactamente la funcionalidad que le pasé a Jim en ese momento. Se trataba de un código (que sigo usando hoy) que permite mover una región. Se ve que lo había obtenido hace poco, porque el commit en mi configuración fue el mismo mes que Jim lo agregó a la suya. Ya ahí se podía ver la diferencia entre lo ordenado y modular de la configuración de Jim en contraste con la mía. Pero eso fue parte de lo bueno, ese día aprendí muchísimo y me motivó a seguir usando Emacs. Lamentablemente no tengo idea quién fue el autor original del código en cuestión.

    Con Federico Iachetti hablamos varias veces de hacer algo sobre Emacs, pero por alguna razón yo siempre me terminaba colgando o la vida terminaba pasando y por ahora no hemos hecho nada. Si siguen el enlace a su blog van a ver un montón de posts sobre Emacs. Y capaz que algún día finalmente me pongo las pilas y hacemos algo juntos!

    Más recientemente en mi trabajo actual, Cultivate, me encontré con más usuarios de Emacs. Más bien usamos Spacemacs, para compartir un ambiente en común al hacer pair programming. Spacemacs es algo así como una “distribución” de Emacs que incluye un modo para replicar el comportamiento de Vim (hace tiempo que estoy queriendo escribir algo al respecto). En el blog de Cultivate escribimos (en inglés) sobre cómo tener un ambiente de desarrollo Ruby en Spacemacs y cómo tener una configuración compartida con capas privadas. Lo interesante es que al haber más personas usando el mismo editor, aprendemos cosas nuevas y lo compartimos con el resto. Y vamos viendo cosas interesantes que se pueden hacer y agregando funcionalidad o temas.

    Ese ambiente de constante retroalimentación, motivación y aprendizaje es súper productivo, y nos hace sentirnos cada vez más cómodos con la herramienta que usamos. Con todo esto voy al tema que a veces una persona se siente un poco “rara” o “desubicada” por ser quien usa Emacs en vez del editor de texto o IDE que esté de moda en el momento. Me pasa seguido, capaz que es parte de la condición humana, o simplemente soy raro. Pero esos momentos me generan un poco de frustración y no son divertidos. Es más o menos lo mismo que me pasa generalmente por ser la única persona en la empresa que usa Linux.

    Pero si se encuentran en esa misma situación, siéntanse en su casa en este blog. Hay varios posts sobre Emacs, algunos menos serios que otros. Los comentarios y demás vías de contacto están abiertos para hablar de Emacs, Linux, la inmortalidad del cangrejo, el nuevo cómic de Avengers de Jason Aaron y Ed McGuinness, los paisajes de Escocia, o lo que sea.

    También recomiendo el blog de Sacha Chua, que ha escrito cosas maravillosas sobre Emacs, aparte de publicar noticias semanales. Otra persona que nos ayuda a no sentirnos mal por usar tan divertido e interesante editor de texto. Y si todo este texto les genera intriga y quieren aprender a usar Emacs, no se pierdan sus guías:

    Blog Bitix: Depuración con la instrucción debugger en JavaScript

    $
    0
    0
    JavaScript

    Los navegadores web utilizan la palabra clave sentencia debugger; como punto de ruptura para iniciar el depurado de un código JavaScript. En vez de poner el punto de ruptura desde el navegador utilizando la herrmienta de depuración para desarrolladores con esta instrucción se puede poner en el código fuente donde se desee, de esta forma se evita buscar entre los múltiples recursos que haya cargados en la página e ir a la línea en la que se desea iniciar la depuración poniendo un punto de ruptura.

    Suponiendo que se desea poner un punto de ruptura en un código JavaScript hay que editar el archivo JavaScript de código fuente e incluir la sentencia debugger; como en el siguiente caso. Cuando el navegador ejecute esa sentencia iniciará, tiendo las herramientas de depuración abiertas, el depurador.

    JavaScript Debugger

    Con la herramienta de depuración abierta se pueden poner expresiones de inspección, nuevos puntos de ruptura e ir paso a paso en la ejecución del código JavaScript. Esta sentencia para iniciar la depuración es lo mismo que añadir un punto de ruptura inspeccionando los recursos de JavaScript de la página pero quizá resulta más cómodo.

    Referencia:

    Blog Bitix: Depurar código JavaScript con la instrucción debugger

    $
    0
    0
    JavaScript

    Los navegadores web utilizan la palabra clave sentencia debugger; como punto de ruptura para iniciar el depurado de un código JavaScript. En vez de poner el punto de ruptura desde el navegador utilizando la herrmienta de depuración para desarrolladores con esta instrucción se puede poner en el código fuente donde se desee, de esta forma se evita buscar entre los múltiples recursos que haya cargados en la página e ir a la línea en la que se desea iniciar la depuración poniendo un punto de ruptura.

    Suponiendo que se desea poner un punto de ruptura en un código JavaScript hay que editar el archivo JavaScript de código fuente e incluir la sentencia debugger; como en el siguiente caso. Cuando el navegador ejecute esa sentencia iniciará, tiendo las herramientas de depuración abiertas, el depurador.

    JavaScript Debugger

    Con la herramienta de depuración abierta se pueden poner expresiones de inspección, nuevos puntos de ruptura e ir paso a paso en la ejecución del código JavaScript. Esta sentencia para iniciar la depuración es lo mismo que añadir un punto de ruptura inspeccionando los recursos de JavaScript de la página pero quizá resulta más cómodo.

    Referencia:

    Adrianistán: Fabric, automatiza tareas en remoto

    $
    0
    0

    En la medida de lo posible tenemos que automatizar todo lo posible. No solo reducimos tiempo sino que reducimos errores, y a la vez, estamos documentando un proceso. Muchas de estas ideas ya las dejé claras en ¡Haz scripts! Sin embargo, hay situaciones donde es más complicada esta situación, como por ejemplo, trabajar con máquinas remotas. Aquí entra Fabric en acción, una librería para Python que permite automatizar procesos en remoto a través de SSH.

    Originalmente Fabric, compatible únicamente con Python 2, funcionaba a través de unos archivos llamados Fabfile que se ejecutaban con el comando fab. Este mecanismo ha sido transformado por completo en Fabric 2, que ha salido hace nada. Yo ya he hecho la transición y puedo decir que el nuevo sistema, más enfocado a ser una librería sin más, es mucho más práctico y útil que el anterior.

    Instalando Fabric

    Voy a usar Pipenv, que es la manera recomendada de gestionar proyectos en Python actualmente.

    pipenv install fabric==2.0.1

    Y accedemos al virtualenv con:

    pipenv shell

    Conexiones

    El elemento fundamental de Fabric a partir de la versión 2 son las conexiones. Estos objetos representan la conexión con otra máquina y podemos:

    • ejecutar comandos en el shell de la otra máquina, run, sudo.
    • descargar archivos desde la máquina remota a local, get
    • subir archivos de local a remoto, put
    • hacer forwarding, forward_local, forward_remote.

    Para iniciar una conexión necesitamos la dirección de la máquina y alguna manera de identificarnos. En todo el tema de la autenticación Fabric delega el trabajo en Paramiko, que soporta gran variedad de opciones, incluida la opción de usar gateways.

    Vamos a mostrar un ejemplo, en este caso, de autenticación con contraseña:

    from fabric import Connection
    from getpass import getpass
    
    password = getpass(prompt="Password for Numancia: ")
    numancia = Connection(host="192.168.0.158",user="roma",connect_kwargs={"password" : password})

    El objeto creado con Connection ya lo podemos usar. Habitualmente no es necesario cerrar la conexión aunque en casos extremos puede ser necesario, una manera es llamando a close, otra es con un bloque with.

    Tareas

    Una vez que ya tenemos la conexión podemos pasárselo a diferentes funciones, cada una con una tarea distinta. Veamos algunos ejemplos.

    Actualizar con apt

    def update(cxn):
        cxn.run("sudo apt update")
        cxn.sudo("apt upgrade -y")

    Los comandos que necesiten sudo pueden necesitar una pseudo-terminal para preguntar la contraseña (si es que la piden, no todos los servidores SSH piden contraseña al hacer sudo). En ese caso añadimos, pty=True.

    def update(cxn):
        cxn.run("sudo apt update",pty=True)
        cxn.run("sudo apt upgrade -y",pty=True)

     

    Backup de Mysql/Mariadb y cifrado con gpg

    def backup_sql(cxn):
        date = time.strftime("%Y%m%d%H%M%S")
        filename = "/tmp/blog-backup-"+date+".sql.gz"
    
        cxn.run("sudo mysqldump blog | gzip > "+filename)
        local_filename = "backups/"+os.path.basename(filename)
        cxn.get(filename,local_filename)
        cxn.run("sudo rm "+filename)
        os.system("gpg --cipher-algo AES256 -c "+local_filename)
        os.remove(local_filename)

    Requiere que MySQL/MariaDB esté configurado con la opción de autenticación POSIX (modo por defecto en el último Ubuntu)

    Como véis, usar Fabric es muy sencillo, ya que apenas se diferencia de un script local.

    Si queremos obtener el resultado del comando, podemos simplemente asignar a una variable la evaluación de los comandos run/sudo.

    def isLinux(cxn):
        result = cxn.run("uname -s")
        return result.stdout.strip() == "Linux"

    Grupos

    Fabric es muy potente, pero en cuanto tengamos muchas máquinas vamos a hacer muchas veces las mismas tareas. Podemos usar un simple bucle for, pero Fabric nos trae una abstracción llamada Group. Básicamente podemos juntar conexiones en un único grupo y este ejecutas las acciones que pidamos. Existen dos tipos de grupo, SerialGroup, que ejecuta las operaciones secuencialmente y ThreadGroup que las ejecuta en paralelo.

    from fabric import ThreadingGroup
    
    def update(cxn):
        cxn.run("sudo apt update")
    
    
    pool = ThreadingGroup("roma@192.168.0.158","madrid@192.168.0.155")
    update(pool)

    O si tienes ya objetos de conexión creados:

    from fabric import ThreadingGroup
    
    def update(cxn):
        cxn.run("sudo apt update")
    
    pool = ThreadingGroup.from_connections([roma,madrid])
    update(pool)

    Con esto ya deberías saber lo básico para manejar Fabric, una fantástica librería para la automatización en remoto. Yo la uso para este blog y otras webs. Existen alternativas como Ansible, aunque a mí nunca me ha terminado de gustar su manera de hacer las cosas, que requiere mucho más aprendizaje.

    La entrada Fabric, automatiza tareas en remoto se publicó primero en Adrianistán.

    Blog Bitix: Recuperar datos eficientemente en GraphQL usando batching

    $
    0
    0
    GraphQL

    Dada una consulta con los datos a recuperar GraphQL hace una llamada al correspondiente resolver o data fecher para obtener el valor de cada propiedad. Cuando se tratan de propiedades en un java bean esto no supone ningún problema en cuanto a rendimiento pero cuando obtener el valor de una propiedad es costoso la consulta resulta ineficiente.

    Por ejemplo, siguiendo el ejemplo que he utilizado en anteriores artículos sobre GraphQL de una librería en la que hay un tipo para representar un libro con una propiedad con sus comentarios, con una consulta que permite recuperar los libros para cada uno de ellos se llama al resolver que recupera los comentarios. En este ejemplo no ya que están los datos en memoria y no se usa una base de datos pero si recuperar los comentarios de cada libro supusiera una consulta SQL en una base de datos relacional (o tráfico de red en una base de datos NoSQL u otro servicio) y la lista de libros devuelta fuese grande cada vez que se realizará esta consulta el número de sentencias SQL a ejecutar sería grande y el tiempo de respuesta pobre y con una carga mayor para el servidor de base de datos.

    Para hacer eficientemente este caso en GraphQL existe la funcionalidad de batching con la que un resolver o data fecher puede recuperar los comentarios de todos los libros en una misma petición. Para esto GraphQL proporciona al resolver en vez de cada libro individual la lista de todos los libros para los cuales hay recuperar los comentarios.

    Esta es la teoría ya que en el momento de escribir este artículo en la librería de utilidades que hace más sencillo usar GraphQL en Java se implementó una petición de mejora para añadir batching a los resolvers, en su momento se añadió la funcionalidad pero no de forma correcta como me di cuenta a escribir y probar el ejemplo de esta serie de artículos de modo que les creé esta petición para corregir el soporte de batching. Tres días depués de haber creado la petición en GitHub alguien envío un pull request pero no ha sido hasta después de casi seis meses que finalmente se ha aceptado, fusionado y publicado en la versión 5.1.0.

    La firma del método del resolver para recuperar los comentarios de un libro sin usar batching y usando batching son los siguientes respectivamente. Estos al igual que cualquier otro método del resolver puede recibir parámetros con los que implementar la funcionalidad que se desee, en este caso para limitar el número de resultados devueltos y para devolverlos a partir de uno dado.

    Con la lista completa de libros de la que hay que recuperar los comentarios ya sería posible lanzar una única consulta SQL a una base de datos relacional en vez de una por cada libro. El método que emplea batching ha de estar anotado con la anotación @Batched.

    La consulta de GraphQL a realizar para recuperar los tres primeros comentarios de cada libro y los resultados que devuelve son los siguientes. La consulta parece un tanto compleja porque la propiedad de los comentarios implementa paginación pero básicamente se recupera de cada libro su título y los comentarios.

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradew run.

    Poesía Binaria: Implementación de filtros de contenido “después de la cola” en Postfix [Ejemplos en Bash y Python]

    $
    0
    0

    Filros de contenido en Postfix
    Como hemos visto en anteriores artículos, Postfix nos da una gran flexibilidad a la hora de aplicar reglar y filtros a los mensajes que van llegando. Como ejemplo tenemos la implementación de Milters. Estos filtros se ejecutan antes de introducir los mensajes en la cola de entrega por lo que, a veces, puede no ser lo que buscamos. Cuando filtramos un correo con milters, el emisor del mensaje va a saber si se entrega o no. Esto puede ser un punto positivo, pero cuando se trata de combatir el SPAM no podemos dejar que alguien se ponga a hacer pruebas contra nuestro servidor hasta que descubra nuestras reglas. Por otro lado, podemos tener necesidades especiales, como desviar el proceso de entrega de un mensaje cuando cumple una serie de reglas. Imaginémonos que cuando el mensaje tenga un determinado destinatario debemos enviarlo conectando directamente con un servidor de correo determinado. Puede que esto parezca un capricho, pero en determinados entornos empresariales, por seguridad, se suelen filtrar a conciencia los correos y tenemos que ser los demás los que nos adaptemos a ellos.

    ¿Dónde entran los filtros de contenido “después de la cola”?

    Este tipo de filtro, como su nombre indica, after-queue, o después de la cola, se llamarán cuando el mensaje ya está en la cola de entrega. Aún tienen que pasar por un proceso para ser entregados, pero el emisor, o el servidor que los ha dejado, ya los ha enviado y ha desconectado, por lo que el control está ahora en nuestro campo. Cuando utilizábamos los Milters, teníamos bibliotecas para varios lenguajes de programación que nos permitían controlar el proceso. De hecho, libmilter nos permitía implementar el protocolo y manejo de estos filtros de forma sencilla. En cambio, con este tipo de filtros, después de la cola, vamos a recibir el mensaje completo, en texto plano, y nosotros nos tenemos que encargar de parsear el contenido del mensaje, extraer las cabeceras, analizarlas y actuar en consecuencia. Es algo más artesano, pero muy útil.

    Gracias a la flexibilidad que nos da Postfix podemos implementar los filtros de varias formas. Por un lado, si el filtro es muy rápido, no va a tardar mucho en analizar los correos, incluso si son muy grandes, podemos hacer un simple script en bash, perl, python, PHP, un programa en C o lo que queramos, que simplemente recibirá el mensaje y lo enviará al siguiente paso de nuestro proceso de envío de correos. Vamos, Postfix saca de la cola el mensaje y se lo manda a nuestro filtro, nuestro filtro lo procesa y lo meterá de nuevo en la cola. Así que, un simple script puede hacer las veces de filtro de contenido, muy fácil y rápido de implementar.

    Por otro lado, si queremos complicarnos un poco la vida, y no sólo complicarnos la vida, sino pensar en la escalabilidad del sistema, hacer que nuestro filtro lo procese otra máquina, un programa dentro de un contenedor de docker o lo que se nos ocurra, debemos implementar el filtro de contenido en un interfaz por red. Para ello, tenemos que crear un pequeño servidor de correo que, cuando reciba un mensaje, lo procese, y tome decisiones. Si queremos que el mensaje se envíe a través de nuestro sistema, tenemos que devolverlo a nuestro Postfix de nuevo, enviando el mensaje a través de otro puerto de envío. Si no queremos enviarlo, no tenemos que hacer nada, una vez el mensaje se ha sacado de la cola, desaparece.

    Algunas utilidades, algo menos resumido

    Uno de los usos más extendido es implementar filtros AntiSpam. Aunque hay muchos servidores de correo que cuando alguien envía un correo basura rechazan el mensaje nada más entrar, podemos estar dando pistas a un posible spammer. Por otro lado, muchas veces, los filtros AntiSpam suelen ser más complicados, implican consultar con varios servicios online de listas blancas y negras, aparte del análisis de la información, por lo que puede retrasar mucho el envío de los mensajes. ¿Por qué no sacar los mensajes de la cola, procesarlos, tardar lo que tengamos que tardar y volver a encolarlos?
    Otro uso muy extendido es el de la búsqueda de virus o malware. Aunque, uno de mis usos favoritos es el poder cambiar la trayectoria de los correos. Me refiero a que si mandamos los correos a una dirección de desarrollo, no pase por el sistema de correo, sino que llegue a una base de datos, o a un fichero, o incluso pase directamente a un programa de análisis, o que si el mensaje es para un cierto destinatario, podamos cambiar el remitente y enviarlo desde otro servidor diferente. En este último caso, sacamos el mensaje de la cola de envío y, si el mensaje cumple unas ciertas condiciones lo volveremos a enviar, a través de otro servidor de correo, si no las cumple, el mensaje seguirá su curso. Es muy divertido.

    Eso sí, os recomiendo encarecidamente que, cuando implementéis vuestros filtros de correo, escribáis en un log el mensaje que ha llegado y lo que hacéis con él. Esto nos ayudará muchísimo para el diagnóstico de problemas. Porque es posible que nuestro filtro no funcione bien, o que el mensaje que ha llegado no cumpla bien las condiciones, o incluso que haya un problema al volver a meterlo en la cola.

    Configurando un filtro de contenido con un script

    Como ejemplo sencillo, vamos a empezar configurando un script como filtro de correo. Es más, un script para Bash, muy sencillo, con el que recibiremos el mensaje, lo procesaremos y se lo volveremos a enviar a Postfix. Podemos crear un directorio llamado /var/spool/filters e introducirlos ahí. Por ejemplo el siguiente (lo llamamos censurado.sh):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #!/bin/sh

    function error(){
      localCODE="$1"
      localMSG="$2"
      echo"$MSG (Code: $CODE)"
      exit$CODE
    }

    INSPECT_DIR=/tmp
    SENDMAIL="/usr/sbin/sendmail -G -i"
    TMPFILE="in.$$"

    # Códigos de salida obtenidos de <sysexits.h>
    EX_TEMPFAIL=75
    EX_UNAVAILABLE=69

    # Limpieza de archivos temporales
    trap"rm -f $TMPFILE"012315
    # Start processing.
    cd$INSPECT_DIR|| error $EX_TEMPFAIL"$INSPECT_DIR does not exist"
    cat>$TMPFILE|| error $EX_TEMPFAIL"Cannot save mail to file"

    if[-n"$(grep -i 'censurado' $TMPFILE)"]; then
      error $EX_UNAVAILABLE"censurado found!!"
    fi

    $SENDMAIL"$@"<$TMPFILE
    exit$?

    Vale, este filtro es muy sencillo, solo busca la palabra “censurado” dentro del texto del mensaje. Si lo encuentra, tira el mensaje para atrás y si no, lo devuelve a la cola. Como veis, para devolverlo a la cola, volvemos a llamar a sendmail. Y, por supuesto, podemos utilizar este script para implementar otros filtros más complicados, filtros que llamen a otros programas como sed, awk, o incluso otros programas externos que hagamos en otros lenguajes, envíen el mensaje a un servidor para su posterior procesamiento, anotar en un log el mensaje, emitir notificaciones, como por ejemplo enviar un SMS al móvil o un mensaje por Telegram a una persona cuando le manden un e-mail con una cierta palabra, vamos, lo que se nos ocurra. Eso sí, es mala idea autocontestar los correos desde aquí, sobre todo porque muchas veces, hay virus o mensajes de SPAM cuyo remitente no es el que dice ser y podemos estar autocontestando mensajes generando SPAM al mismo tiempo.

    Ahora, para que Postfix ejecute este pequeño script cada vez que envíe un e-mail editamos /etc/postfix/master.cf buscamos la línea de smtp y la modificamos de la siguiente forma:

    1
    2
    3
    4
    5
    6
      # =============================================================
      # service type  private unpriv  chroot  wakeup  maxproc command
      #               (yes)  (yes)  (yes)  (never)(100)
      # =============================================================
    smtp    inet    n       -       n       -       -       smtpd
       -o content_filter=censurado:dummy

    Eso sí, el texto que en mi ejemplo son n y – lo dejamos tal y como lo tengamos en nuestro servidor, porque podemos alterar el funcionamiento del servidor de correo. Puede que smtpd tenga más argumentos en su ejecución, deberíamos añadir -o content_filter=censurado:dummy al final de los argumentos. Dummy es una configuración que indica el destino de los mensajes y puede ser utilizado en otros métodos de transporte (distinto de smtpd), pero que, por compatibilidad con versiones antiguas, es buena idea dejarlo escrito. Una vez hecho esto, más abajo en /etC/postfix/master.cf (al final, por ejemplo), debemos configurar nuestro filtro de la siguiente manera:

    1
    2
    3
    4
    5
    6
    # ==========================================================================
    # service type  private unpriv  chroot  wakeup  maxproc command + args
    #               (yes)  (yes)  (yes)  (never)(100)
    # ==========================================================================
    censurado unix -       n       n       -       -       pipe
       flags=Rq user=mail argv=/var/spool/filters/censurado.sh -f ${sender} -- ${recipient}

    Una vez hecho esto, podemos reiniciar Postfix y ver si todo funciona bien. Algo que me gusta hacer siempre, cuando un sistema no está en producción y estamos creando filtros es meter llamadas a logger “Paso XXX” donde XXX pueden ser números o una explicación de qué estamos haciendo dentro del fichero de script de filtro. Luego, cuando haga las llamadas pertinentes al servidor de correo, donde se supone que se ha llamado al filtro, miro /var/log/syslog a ver si esos mensajes aparecen. Solo por saber que todo se ejecuta bien. Luego esas trazas las podemos eliminar o desactivar para poder activarlas en cualquier momento y diagnosticar problemas.

    Configurando un filtro de contenido a través de TCP

    En esta segunda opción, vamos a implementar un filtro a través de un mini-servidor SMTP que vamos a crear en Python. El objetivo es que sea otra máquina diferente la que pueda procesar el filtro. Donde digo otra máquina, puedo decir un servicio en la misma máquina (que en el futuro me podré llevar a otro lado) o un contenedor. Pero el caso es que se ejecutará por separado. Ahora Postfix, llamará a nuestro servicio a través de un puerto TCP y nuestro servicio volverá a enviar el mensaje a través de otro puerto diferente para meterlo en la cola. Y, si no queremos meterlo en la cola, no hacemos nada y listo.

    He elegido hacer el script en Python porque es más sencillo de hacer y de entender, pero podríamos elegir cualquier lenguaje de programación que queramos. Ahora, se enviarán comandos SMTP y nosotros enviaremos comandos SMTP. Nuestro script /var/spool/filters/censurado.py quedaría así:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    importsmtpd
    importasyncore

    importsmtplib

    importtraceback
    from Milter.utilsimport parse_addr

    class CustomSMTPServer(smtpd.SMTPServer):

        def process_message(self, peer, mailfrom, rcpttos, data):
           
            mailfrom.replace('\'','')
            mailfrom.replace('"','')
           
            for recipient in rcpttos:
                recipient.replace('\'','')
                recipient.replace('"','')
           
            print'Recibo un mensaje desde:', peer
            print'Remitente:', mailfrom
            print'Destinatario  :', rcpttos
            print'MSG >>'
            print data
            print'>> EOT'

                    if'censurado'in data:
                print'E-mail censurado'
                return


            try:
                server =smtplib.SMTP('localhost',10026)
                server.sendmail(mailfrom, rcpttos, data)
                server.quit()
                print'send successful'
            exceptsmtplib.SMTPException:
                print'Exception SMTPException'
            except:
                print'Undefined exception'
                printtraceback.format_exc()

            return
           
    server = CustomSMTPServer(('127.0.0.1',10025),None)

    asyncore.loop()

    En este caso, nuestro filtro escuchará en el puerto 10025 y cuando un mensaje pase el filtro lo enviará por SMTP al puerto 10026. Ahora, como siempre, tenemos que decirle a Postfix todo esto. Para ello, editamos /etc/postfix/master.cf (al final, por ejemplo) con lo siguiente:

    1
    2
    3
    4
    5
    # Recibe los mensajes
    censurado unix - - n - 10 smtp -o smtp_send_xforward_command=yes -o disable_mime_output_conversion=yes

    # Mete los mensajes de nuevo en la cola
    localhost:10026 inet n - n - 10 smtpd -o content_filter= -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters -o smtpd_authorized_xforward_hosts=127.0.0.0/8

    Ahora en /etc/postfix/main.cf añadimos estas línea:

    1
    content_filter= censurado:localhost:10025 receive_override_options = no_address_mappings

    En este caso estamos haciendo todo en localhost, pero si tuviéramos el servicio en otra máquina, deberíamos cambiar todos los localhost y los 127.0.0.1. Yo recomiento siempre dar nombres de hosts, aunque estemos en una red pequeña con máquinas conocidas. Podemos asociar en /etc/hosts el nombre del host con la IP que corresponda dentro de la red local. En /var/spool/filters/censurado.py deberíamos cambiar, CustomSMTPServer((‘127.0.0.1’, 10025), None) por la IP o el host de la red local de la máquina donde está instalado el filtro; y en smtplib.SMTP(‘localhost’, 10026), localhost debería ser la IP o el host de la máquina donde está instalado Postfix. Luego en /etc/postfix/main.cf , deberíamos cambiar localhost por el host de la máquina que ejecuta los filtros y en /etc/postfix/master.cf, localhost:10026 será el host de la máquina que ejecuta Postfix, podremos cambiar también smtpd_authorized_xforward_hosts= por la red local donde estemos ejecutando Postfix y los filtros, por ejemplo 172.0.0.0/24.

    Tras esto debemos reiniciar Postfix. Ahora ejecutamos el script de Python que ejecuta el filtro, lo dejamos ejecutando e intentamos enviar un mail con esta configuración. La ventana donde tenemos el script de Python en ejecución debería empezar a mostrar información sobre la información recibida.

    Si todo funciona bien, deberíamos configurar nuestro servicio de filtro en Python como servicio de sistema, para ejecutarse en el inicio y para tener una gestión más cómoda. Podemos seguir estos pasos.

    Foto principal: unsplash-logoTom Parsons

    The post Implementación de filtros de contenido “después de la cola” en Postfix [Ejemplos en Bash y Python] appeared first on Poesía Binaria.

    Variable not found: Enlaces interesantes 322

    $
    0
    0
    Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

    .NET / .NET Core

    ASP.NET / ASP.NET Core

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

    Data

    HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publicado en Variable not found.

    Picando Código: Humble Book Bundle: libros digitales sobre Desarrollo y Diseño Web por O’Reilly

    $
    0
    0

    Nuevo paquete de libros digitales sobre Diseño y Desarrollo Web por O’Reilly. Paga el precio que elijas por hasta 15 libros sobre JavaScript, CSS, HTTP/2, SVG y más.

    Humble Book Bundle: Web Design & Development

    Pagando el monto mínimo de USD 1, obtenemos: Programming Voice Interfaces, Mobile App Development with Ionic, JSON at Work, Refactoring JavaScript y CSS Refactoring.

    Por USD 8 o más, obtenemos también: Interactive Data Visualization for the Web, Learning React Native, Learning HTTP/2, SVG Animations y Working with Static Sites.

    Por último, pagando USD 15 o más, el paquete incluye React Native Cookbook, CSS: The Definitive Guide, Using SVG with CSS3 and HTML5, Learning React y High Performance Images.

    Los ebooks están disponibles en PDF, ePUB y MOBI, por lo que pueden ser leídos en casi cualquier dispositivo. Como se acostumbra en los paquetes Humble Bundle, además de elegir el precio, podemos elegir dónde destinamos el dinero que pagamos, incluyendo una organización de beneficencia. En este caso se trata de Code for America, una fundación sin fines de lucro que trabaja con sociedad civil y el gobierno para mejorar las comunidades.

    Visita Humble Book Bundle para comprar el paquete de libros

    Humble Book Bundle: Web Design &amp; Development

    Variable not found: Usar Razor desde una aplicación de consola .NET Core (2 de 2)

    $
    0
    0
    ASP.NET CoreRecordaréis que hace un par de semanas iniciamos un pequeño viaje en el que nuestro objetivo era renderizar una vista Razor de forma totalmente manual, desde procesos externos a ASP.NET Core y, por supuesto, introduciendo el menor número posible de dependencias hacia este framework. Ya comentamos entonces que este proceso consistía básicamente en:
    • Procesar la plantilla CSHTML y obtener código C# que permita renderizarla.
    • A continuación, compilar dicho código para obtener un componente ejecutable.
    • Por último, ejecutar el código para renderizar la plantilla.
    En el primer post vimos cómo implementar el primero de los puntos, por lo que ahora nos centraremos en los siguientes, y conseguiremos el objetivo pretendido.

    ¡Seguimos! ;)

    Cómo compilar el código C# de la plantilla

    En el artículo anterior fuimos capaces de procesar una plantilla Razor (.cshtml) y generar código C# desde ella utilizando la clase RazorGenerator que creamos a lo largo del mismo. En aquél momento, por tanto, ya podíamos ejecutar un código como el siguiente:
    var generator = new RazorGenerator("Templates");
    var sourceCode = generator.GenerateCode<string>("Hello"); // Templates/Hello.cshtml
    El siguiente paso es compilar el código fuente que nos devuelve el método GenerateCode() en forma de cadena de caracteres. Nos valdremos para ello de la magia de Roslyn, por lo que lo primero que debemos hacer es añadir a nuestra aplicación el paquete NuGet Microsoft.CodeAnalysis.CSharp.

    A continuación crearemos una clase como la siguiente, que ya deja entrever hacia dónde nos dirigimos. El método CreateTemplateInstance() recibe el código fuente y el nombre de la plantilla para compilarlo y retorna una instancia de la plantilla lista para ser utilizada. Llamaremos a esta clase RazorCompiler:
    publicclass RazorCompiler
    {
    publicstatic TemplateBase<TModel> CreateTemplateInstance<TModel>(
    string sourceCode, string templateName)
    {
    var generatedAssembly = GenerateAssembly<TModel>(sourceCode, templateName);
    return LoadTemplateClassFromAssembly<TModel>(generatedAssembly, templateName);
    }

    privatestaticstring GenerateAssembly<TModel>(string sourceCode, string templateName)
    {
    // Compiles the code and returns the path to the generated assembly
    ...
    }

    privatestatic TemplateBase<TModel> LoadTemplateClassFromAssembly<TModel>(
    string assemblyFilePath, string templateName)
    {
    // Loads the assembly and creates an instance of TemplateBase<TModel>
    ...
    }
    }
    Echemos primero un vistazo a GenerateAssembly(), cuyo objetivo se centra exclusivamente en compilar código C# y generar un ensamblado (.DLL):
    privatestaticstring GenerateAssembly<TModel>(string sourceCode, string templateName)
    {
    var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
    var coreAssemblyPath = typeof(object).Assembly.Location;
    var coreAssemblyFolder = Path.GetDirectoryName(coreAssemblyPath);
    var references = new[]
    {
    // Add required framework references. See https://stackoverflow.com/a/47756437
    MetadataReference.CreateFromFile(coreAssemblyPath),
    MetadataReference.CreateFromFile(Path.Combine(coreAssemblyFolder, "System.Runtime.dll")),
    MetadataReference.CreateFromFile(Path.Combine(coreAssemblyFolder, "NetStandard.dll")),

    // We need to use TemplateBase<>, so let's add a reference to its assembly
    MetadataReference.CreateFromFile(typeof(TemplateBase<>).Assembly.Location),
    // And also we need to add the assembly where TModel is defined
    MetadataReference.CreateFromFile(typeof(TModel).Assembly.Location),
    };

    var options = new CSharpCompilationOptions(
    outputKind: OutputKind.DynamicallyLinkedLibrary,
    optimizationLevel: OptimizationLevel.Release
    );

    var className = $"{typeof(TemplateBase<>).Namespace}.Templates.{templateName}";
    var compilation = CSharpCompilation.Create(className,
    syntaxTrees: new[] { syntaxTree },
    references: references,
    options: options);

    var currentAssemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    string outputFile = Path.Combine(currentAssemblyPath, $"{className}.dll");
    var result = compilation.Emit(outputFile);
    if (!result.Success)
    {
    thrownew Exception(
    "Compilation error: " + string.Join(Environment.NewLine, result.Diagnostics)
    );
    }
    return outputFile;
    }
    Como se puede observar, el método GenerateAssembly() recibe como argumentos el código fuente que debe compilar y el nombre de la clase que vamos a generar (que, como ya convenimos en el post anterior, coincidiría con el de la plantilla). El código fuente lo parseamos y creamos con él un árbol sintáctico que luego compilamos haciendo uso un objeto CSharpCompilation configurado para que emita un ensamblado en la carpeta de binarios del proyecto.

    El resultado de la ejecución de este método será un ensamblado que seguirá la convención que estamos definiendo. Por ejemplo, para una plantilla llamada Test.cshtml, se generará el archivo <Root>.Templates.Test.dll, siendo <Root> el espacio de nombres donde se ha definido la clase TemplateBase<>.

    Cómo cargar en memoria el ensamblado generado

    La ruta del ensamblado generado es retornada y utilizada como entrada para la llamada al método LoadTemplateClassFromAssembly(), que lo cargará en memoria y obtendrá de él una instancia de TemplateBase<T>, donde T es el tipo de la propiedad Model de la plantilla. Su código es casi trivial:
    privatestatic TemplateBase<TModel> LoadTemplateClassFromAssembly<TModel>(
    string assemblyFilePath, string templateName)
    {
    var asm = Assembly.LoadFile(assemblyFilePath);
    var templateType = asm.GetType(
    $"{typeof(TemplateBase<>).Namespace}.Templates.{templateName}"
    );
    var template = (TemplateBase<TModel>)Activator.CreateInstance(templateType);
    return template;
    }

    Cómo ejecutar la plantilla y obtener el resultado de la renderización

    Por fin llegamos a la parte más sencilla ;) Una vez que ya tenemos la instancia de la plantilla, sólo tenemos que cargar llamar al método ExecuteAsync() suministrándole el TextWriter que éste debe utilizar para emitir el resultado de la ejecución de la plantilla y el objeto del modelo que utilizará ésta para renderizarse.

    O expresado en forma de código, sería así de sencillo:
    ...
    var templateInstance = RazorCompiler.CreateTemplateInstance<TModel>(code, templateName);
    var sb = new StringBuilder();
    var output = new StringWriter(sb);

    templateInstance.ExecuteAsync(model, output);
    var result = sb.ToString(); // Voila!

    ¿Lo vemos todo junto?

    El método RenderTemplate() que veremos a continuación implementa el proceso completo: usará nuestra clase RazorGenerator para parsear la plantilla y generar el código C#, y la clase RazorCompiler para obtener la instancia de la clase generada anteriormente. Finalmente, ejecutamos la plantilla sobre un StringBuilder, del cual obtenemos el resultado del renderizado:
    publicstaticstring RenderTemplate<TModel>(string templateName, TModel model)
    {
    var generator = new RazorGenerator("Templates");
    var code = generator.GenerateCode<TModel>(templateName);
    var template = RazorCompiler.CreateTemplateInstance<TModel>(code, templateName);
    var sb = new StringBuilder();
    var output = new StringWriter(sb);
    template.ExecuteAsync(model, output);
    return sb.ToString();
    }
    Y unos ejemplos de uso de este método podrían ser los siguientes:
    class Program
    {
    staticvoid Main(string[] args)
    {
    Console.WriteLine("Templates/Hello.cshtml, with string model");
    Console.WriteLine(RenderTemplate("Hello", "Peter"));
    Console.ReadLine();

    Console.WriteLine("Templates/Multiplication.cshtml, with int model");
    Console.WriteLine(RenderTemplate("Multiplication", 5));
    Console.ReadLine();

    Console.WriteLine("Templates/TemplateWithModel.cshtml with custom model");
    var templateModel = new MyModel { Name1 = "John", Name2 = "Peter"};
    Console.WriteLine(RenderTemplate("TemplateWithModel", templateModel));
    Console.ReadLine();
    }

    publicstaticstring RenderTemplate<TModel>(string templateName, TModel model)
    {
    ... // As seen before
    }
    }

    publicclass MyModel
    {
    publicstring Name1 { get; set; }
    publicstring Name2 { get; set; }
    }

    Conclusión

    ¡Y esto es todo! Con el código que hemos visto ya conseguimos nuestro objetivo, que era renderizar plantillas Razor desde una aplicación de consola o cualquier tipo de proyecto no ASP.NET Core. Pero lo más importante es que durante su creación hemos aprendido un poco más del funcionamiento interno de Razor :)

    Obviamente el código que hemos visto aquí es bastante mejorable, pero creo que ofrece un buen punto de partida para desarrollar soluciones más complejas. Os apunto un par de ideas:
    • En este momento, cada vez que renderizamos la plantilla se ejecuta el proceso completo: generar código, compilarlo, cargar el ensamblado y renderizar la plantilla. Sin embargo, si el ensamblado ya existe y el archivo no ha sido modificado con anterioridad, podríamos saltarnos tanto la generación de código como la compilación e ir directamente a cargar el ensamblado (si ha sido cargado previamente) y al renderizado.
       
    • Otra mejora que podemos implementar, aunque sería bastante más complejo, es la recompilación automática de plantillas en tiempo de ejecución. Esto lo podríamos conseguir detectando si la plantilla ha sido modificada después de generar el ensamblado (comparando fechas o mediante hashes), y volviendo a compilarla. La dificultad aquí reside en que si un ensamblado está en uso no podemos sobrescribirlo, por lo que deberíamos implementar alguna suerte de shadow copy o usar alguna estrategia de nombrado que evite esta sobrescritura.
    ¡Espero que os haya resultado interesante!

    Publicado en Variable not found.

    Blog Bitix: Generador de páginas web estáticas y bitácoras Hugo

    $
    0
    0

    Hugo es un de las mejores herramientas en la categoría de generadores de páginas web estáticas, también puede utilizarse para generar bitácoras. Su fácil instalación, rapidez, personalización o su gran sistema de plantillas y taxonomía son varias de sus características destacadas. Con Hugo conseguí resolver varias de las necesidades que tenía para Blog Bitix, la gestión de enlaces internos y externos, personalización de diseño y procesado de imágenes.

    Hugo

    En diciembre del 2013 pase de usar Blogger a usar Ocotpress junto con GitHub Pages para el hospedaje. En mayo del 2015 modifiqué mi bitácora para usar en vez de Octopress la herramienta equivalente Hugo, ambas sirven para generar sitios web estáticos formados únicamente por HTML, CSS, imágenes, documentos y JavaScript. Tanto Octopress, Jekyll como Hugo y otros como Pelican son similares, a partir de archivos normalmente en formato markdown genera el HTML de la página web o bitácora junto con los recursos estáticos que necesite. Cada una de estas opciones son adecuadas para páginas de presencia en internet y bitácoras.

    Varían en el lenguaje de programación que emplean, Octopress usa Ruby y Hugo usa Go, y el formato de las plantillas que utilizan para personalizar la generación del contenido. La migración desde Blogger a Octopress que hice en 2013 me supuso una gran mejora al editar los artículos. La edición de los artículos se puede hacer con cualquier editor de textos en la computadora local en vez de usar una editor en un navegador mucho más limitado y lento, además de editar el texto la gestión de las imágenes es mucho más simple al tratarse como simples archivos que basta colocar entre el código fuente de la bitácora que usará el generador para producir la página web o bitácora. El cambio de Octopress a Hugo más tarde me permitió otros aspectos importantes a la hora de editar los artículos.

    La web está formada por un conjunto de páginas enlazadas, la gestión de los enlaces es un aspecto muy importante en una página web y en una bitácora es igual. Si una página a la que hacíamos referencia cambia de dirección todos los enlaces que tuviésemos quedarán rotos. Hay varias posibilidades parabuscar enlaces rotos, encontrados y sustituirlos usando Blogger es complicado ya que hay que usar el lento y poco cómodo editor web, usando Octopress era más sencillo haciendo una búsqueda y reemplazo en los archivos de texto en formato markdown con un buen editor de textos pero con Hugo me resulta mucho más sencillo por la forma en que he organizado el código fuente. Los enlaces comunes que utilizo en múltiples artículos los incluyo en un shortcode y otro para los propios artículos del blog y luego en la página o artículo hago referencia a ellos usando un identificador de forma que si un día cambia una página de dirección o una URL de algún artículo solo he de hacer la sustitución en un único lugar en esos shortcodes. Con Disqus externalizo los comentarios.

    La gestión de las imágenes es otro aspecto que si no se organiza se acaba con un directorio sin ninguna organización donde es complicado saber que imágenes utiliza cada artículo. Los principales directorios que utilizo son dos uno para imágenes de logotipos y otro para las imágenes de cada artículo con una subcarpeta por año y otra con el identificador del artículo. Hasta hace poco usaba ImageMagick para producir las previsulizaciones de las imágenes y una reducción de las imágenes originales. Recientemente en Hugo se ha incorporado la funcionalidad de aplicar procesado a las imágenes con lo que es posible generar estas previsualizaciones y reducciones sin necesidad de perder las imágenes originales, esto tiene la ventaja de que si un tiempo después hay que hacer algún cambio a un imagen se puede utilizar la original. También podido simplificar la forma de organizar las imágenes, ahora al lado del artículo markdown en que se usan.

    Tanto la gestión de enlaces como de los recursos estáticos es algo que no podía hacer con Blogger y fue uno de los principales motivos para migrar a un generador estático de páginas web. Como todo son archivos estáticos lo único que hace falta es un servidor web, hay varias opciones de hospedaje bastante baratas al no necesitar un lenguaje de programación en el servidor como PHP ni una base de datos relacional. Yo utilizo GitHub Pages que es incluso sin coste. Usando algunas de las varias formas para monetizar un blog escribiendo contenido interesante y de forma regular los ingresos por este medio cubren el coste del hospedaje y del dominio.

    Con Hugo personalicé completamente el diseño y estilos de de la bitácora pero Hugo dispone de múltiples temas. En la página de documentación de Hugo está el formato a usar en las plantillas, shortcodes y partials así como las variables disponibles y funciones disponibles en las plantillas. Con la guía de inicio rápido es posible tener una página web en unos pocos minutos y la estructura básica de directorios.

    Una de las características destacadas de Hugo es que es muy rápido, en unos pocos segundos es capaz de generar la página web completa a partir del código fuente de los archivos. Ya he escrito más de 300 artículos y tarda menos de 4 segundos en generar este blog. Otra que destaco es que solo se necesita un único binario con lo que se evita el infierno de dependencias que en alguna ocasión con Octopress me causó problemas. Usando el servidor web incorporado que posee es posible previsualizar en local el contenido de la página antes de publicarla.


    Blog Bitix: Empaquetar una aplicación Java en un archivo jar ejecutable incluyendo sus dependencias

    $
    0
    0

    Las aplicaciones Java se distribuyen en uno o varios archivos jar. Si queremos facilitar la distribución de la aplicación con un único archivo jar existe la posibilidad de reempaquetar el jar de la aplicación y sus dependencias en tiempo de ejecución en un nuevo archivo jar que lo contenga todo, a este nuevo jar se le conoce como uberjar o fatjar.

    Java

    La forma de distribuir el código compilado a bytecode en Java es a través de archivos de extensión jar. Los archivos jar no son más que archivos comprimidos den formato zip. Si se les cambia de extensión y se descomprimen se extrae su contenido seguramente con una buena cantidad de archivos de extensión class que es la extensión para los archivos Java compilados a bytecode y que la máquina virtual interpreta para su ejecución. Las librerías que use la aplicación también se deben distribuir junto a esta para que funcione, por lo que la aplicación se distribuirá en forma de una colección de archivos jar.

    Una aplicación distribuida en forma de múltiples archivos archivos jar se ejecuta con una línea de comandos como la siguiente en la que el parámetro -cp indica las ubicaciones donde se buscarán librerías jar y archivos class si se distribuyen de forma individual, el segundo parámetro indica la clase que contiene el método main que inicia la aplicación. Previamente hay que compilar el proyecto con Gradle.

    En este caso se trata de una aplicación que emite un arte en formato ascii en la terminal donde para cada linea se usa un color diferente mediante la librería Jansi que la aplicación tiene como dependencia.

    Aplicación de ejemplo

    Cuando la aplicación está contenida en un archivo jar y se ejecuta con la opción -jar se ignora el parámetro -cp y no se indica la clase main del punto de entrada de la aplicación. En el caso de las aplicaciones distribuidas en un archivo jar tanto la clase main como las dependencias se indican en un archivo de manifiesto incluido en el propio archivo jar. El archivo se ubica en META-INF/MANIFEST.MF dentro del jar, es un archivo de texto donde se indican varias propiedades en forma de atributo y valor, una en cada linea. Un ejemplo de archivo de manifiesto sería el siguiente:

    La propiedad Manifest-Version y Created-By son informativas de la versión del archivo de manifiesto y el autor de la librería jar. La propiedad Main-Class indica la clase main de la librería o aplicación y la propiedad Class-Path es una lista separada por espacios de librerías adicionales. Las propiedades Main-Class y Class-Path son los parámetros que indicamos como parámetros en el comando java anterior. Con el archivo jar, su manifiesto y las librerías la aplicación Java se inicia de forma un poco más sencilla que antes al no tener que indicar ni la clase main ni el classpath.

    Como en este caso, si Java no se encuentra la dependencia de Jansi y se produce la siguiente excepción que indica que no se ha encontrado una clase necesaria.

    Sin embargo, para distribuir la aplicación aún hay que distribuir varios archivos jar, el de la aplicación y los jar de las librerías que necesite la aplicación. En este caso solo es un jar adicional ya que la aplicación solo tiene una dependencia y esta transitivamente no tiene ninguna otra pero en una aplicación más compleja el número de dependencias puede llegar a la centena.

    Para hacer la distribución más sencilla hay una posibilidad que usan algunos programadores de Java que es reempaquetar todas las clases del jar de la aplicación y de las librerías en un nuevo jar, a esta opción de reempaquetar las clases se le conoce como uberjar o fatjar. En la herramienta de construcción Gradle existe un plugin para realizar esta tarea de creación del uberjar pero también se puede hacer definiendo una tarea sin necesidad del plugin. El archivo de Gradle adaptado para producir un uberjar de forma automatizada es el siguiente. La tarea importante en el caso del ejemplo es uberJar.

    Con el siguiente comando la ejecución de la aplicación empaquetada como uberjar produce el mismo resultado. Con el uberjar en el archivo de manifiesto no es necesario incluir el atributo Class-Path ya que todas las clases necesarias tanto de la aplicación como de las dependencias ha sido empaquetadas en el jar.

    Hay un plugin de Gradle para generar uberjars que ofrece varias opciones para filtrar los archivos que se incluyen en el fatjar, fusionar los archivos de servicios que permiten extender funcionalidades y otras tareas para realizar generar el distribuible con distShadowZip y distShadowTar. Spring Boot ofrece algo similar con la tarea bootRepackage pero si no se trata de una aplicación que use Spring Boot lo anterior sirve para cualquier otra aplicación Java como sería el caso de una aplicación de escritorio que utiliza JavaFX.

    Que la aplicación sea un único jar tiene la ventaja que el distribuible es más sencillo y facilita desplegarlo en un entorno de producción, en el caso de usar Docker también es más adecuado un único archivo.

    Entre la documentación hay unas muy buenas guías prácticas sobre Java, una de ellas sobre el empaqueado de apliaciones en archivos jar. La información que se encuentra en estas guías y tutoriales es muy valiosa para cualquier programador que use el lenguaje Java.

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew assemble && java -jar build/libs/JavaUberjar-uberjar.jar.

    Adrianistán: Ayuda a la ciencia desde tu casa con BOINC

    $
    0
    0

    El invento de los ordenadores se lo debemos a los científicos, que buscando maneras de acelerar sus cálculos decidieron crear máquinas que los automatizaban. A partir de esos desarrollos la informática se ha diversificado en muchas más ramas, sin embargo la necesidad de procesar grandes cantidades de datos sigue siendo de vital importancia para la investigación en física, química, medicina y demás ramas de la ciencia.

    El ENIAC, uno de los primeros ordenadores construidos

    Este tipo de cálculos se realizan normalmente en supercomputadoras en clúster que trabajan en paralelo. Sin embargo en muchas ocasiones, nada impide que cada nodo esté separado kilómetros de distancia. Ahí nace el proyecto BOINC (siglas de Berkeley Open Infrastructure for Network Computing). BOINC es un sistema de computación distribuida en Grid. Esto quiere decir que el cómputo necesario para un proyecto se divide en paquetes. Cada paquete se manda a un ordenador que forma parte de la red. Cada ordenador se encarga de procesarlo. Cuando los cálculos han finalizado, los envía al servidor central que le entrega a su vez al ordenador un paquete con cómputos nuevos por realizar.

    De todos los sistemas de computación distribuida en Grid, BOINC es sin duda el más popular. La potencia de cálculo de BOINC es de media 23 petaflops, algo lejos del mayor supercomputador (Sunway TaihuLight, de China, con 93 petaflops) pero superior a muchos supercomptadores como MareNostrum, el mayor supercomputador de España y que tiene 11 petaflops.

    Supercomputador Mare Nostrum 4

    Cualquier ordenador del mundo puede colaborar con la red BOINC aportando su potencia a la red y calculando datos muy importantes para las investigaciones científicas.

    ¿Cómo puedo usar BOINC y ayudar?

    Es muy sencillo. BOINC dispone de versiones para Windows, Mac, Linux y Android, así como versiones optimizadas con CUDA (Nvidia), OpenCL y existen versiones de terceros para Solaris, HP-UX, FreeBSD y otros sistemas.

    El primer paso es descargar BOINC desde https://boinc.berkeley.edu/ . Si estás en Android puedes ir directamente a Google Play y descargar BOINC desde allí.

    El cliente BOINC se divide en dos partes: una que se dedica a calcular y otra que es una interfaz de usuario que nos permite controlar la parte que calcula (BOINC Manager). La ventaja de esto es que son programas independientes y no hace falta tener la interfaz de usuario abierta para seguir calculando.

    Proyectos

    Lo primero que tenemos que hacer nada más abrir el BOINC Manager es registrarnos en un proyecto para que nos empiece a mandar tareas.

    Algunos de los más populares son:

    • SETI@Home. Es el proyecto que busca vida extreterrestre por el universo analizando las señales de radiotelescopios como el de Arecibo. Fue uno de los primeros proyectos de BOINC y uno de los más populares.
    • Rosetta@Home: Este proyecto busca nuevos tipos de proteínas así como su aplicación en enfermedades como la malaria y el alzheimer,.
    • Einstein@Home: Este proyecto busca detectar ondas gravitacionales desde los datos recogidos de observatorios como LIGO.
    • PrimeGrid: Busca nuevos números primos, esenciales en campos como la de la criptografía.
    • ClimatePrediction: Busca modelos climáticos mejorados que permitan mejorar nuestra capacidad de predicción del clima
    • MilkyWay@Home: Investiga modelos tridimensionales sobre nuestra galaxia, la vía láctea
    • LHC@Home: Tareas relacionadas con la investigación en física de partículas con datos generados por el LHC.
    • Asteroids@Home: Amplía nuestro conocimiento sobre los billones de asteroides que hay en el universo, analizando los que podemos ver

    Para registrarnos indicamos nuestro correo y nuestra contraseña y automáticamente todo empezará a funcionar. ¡No hay que hacer nada más! BOINC se encargará de todo automáticamente.

    Desde la interfaz podemos parar los cálculos en cualquier momento pulsando Suspend o Pausar.

    BOINC Credits

    En recompensa a nuestro trabajo se nos dan BOINC Credits. La cantidad de BOINC Credits es proporcional a los gigaflops que hayamos aportado. Estos créditos no sirven realmente para nada. Únicamente para fardar ante tus amigos. Y es que hay gente realmente picada y existen equipos de gente que se unen para superar a otros equipos. Los BOINC Credits también sirven para realizar benchmarks de los ordenadores. Puedes consultar tus BOINC Credits desde BOINC Manager o desde webs externas como BOINC Stats.

    Mis estadísticas en BOINC

    La entrada Ayuda a la ciencia desde tu casa con BOINC se publicó primero en Adrianistán.

    Koalite: Auditoría: Quién ha hecho qué

    $
    0
    0

    Hace poco me preguntaba Alfonso Vargas en twitter sobre patrones para guardar un registro de los cambios realizados en entidades. No conozco ninguna receta mágica para hacerlo, pero sí he usado varias técnicas que a lo mejor pueden resultar útiles a alguien.

    Por cierto, esto es algo que os animo a hacer: preguntar. La mayoría estamos dispuestos a ayudar en la medida de nuestras posibilidades.

    El concepto de auditoría

    Como hice cuando conté que toda aplicación debería tener un log, vamos a empezar por ver a qué nos referimos (o, al menos, a qué me voy a referir yo en este post) con auditoría.

    Una aplicación puede guardar mucha información sobre lo que está pasando, pero no toda la información forma parte de la auditoría. La auditoría está formada por acciones de negocio. Son acciones realizadas por usuarios de la aplicación de las cuales queremos guardar una trazabilidad. Por ejemplo, conocer qué usuario activó una promoción. O cuándo se creó un nuevo producto. O quién canceló un pedido.

    Aquí el concepto de negocio puede ser un tanto difuso, y dependiendo del tipo de aplicación y del tipo de usuarios que tenga, podemos auditar también aspectos como cambios en la configuración, cambios de versión, etc.

    Esto dejaría fuera de la auditoría información como estadísticas de uso, errores, controles de rendimiento o, en general, cosas que interesan más a quién desarrolla la aplicación que a quien la usa.

    Partiendo de esta visión de la auditoría como un registro de acciones de negocio, vamos a ver varias formas de enfocar el problema de cómo gestionarla.

    No hacer nada

    Suena mal, pero muchas veces nos empeñamos en hacer cosas que luego no valen de mucho.

    Generar un buen registro de auditoría, que sea fácil de mantener, se consulte con comodidad y ofrezca información valiosa para el negocio no es trivial, por lo que antes de hacerlo porque sí, planteáte si te lo puedes evitar.

    Generarla a mano

    Sin duda es la opción más tediosa, porque implica que debemos escribir código específico para registrar cada evento de auditoría.

    Depende de cómo esté estructurada la aplicación es posible que acabemos teniendo que intercalar código de auditoría con el código puro “de negocio” y se haga todo un poco más difícil de leer, mantener y testear.

    Por otra parte, si estás generando la auditoría es porque se supone que es importante para el negocio, por lo que tratarla como parte integral del código de negocio tampoco parece tan mala idea.

    La principal ventaja de esta técnica es que, aunque tediosa, es muy simple de implementar y ofrece el máximo control sobre qué auditamos y cómo lo hacemos. Por ejemplo, si en una operación de negocio queremos generar distintos eventos de auditoría en función del resultado o de los parámetros, podemos hacerlo fácilmente. O si necesitamos que una operación de negocio genere más de un evento de auditoría. O que lo haga sólo en determinados casos.

    Además del trabajo que conlleva esta técnica, corremos el riesgo de que se nos olvide auditar determinadas acciones. A fin de cuentas, es algo manual y es fácil que cuando implementas una nueva operación se te olvide que hay que auditarla, o que al cambiar los datos generados por una acción se te olvida modificar los datos auditados.

    Utilizar Event Sourcing

    Si estás usando un diseño basado en Event Sourcing tienes auditoría casi gratis. O al menos es fácil tenerla si tus eventos están bien diseñados, porque deberían representar las acciones de negocio importantes para tu aplicación. Siendo realistas, más que las acciones de negocio son sus consecuencias, por lo que puede que pierdas parte del contexto.

    De todas formas, tal vez eso sea demasiado optimista y haya unos cuantos eventos que no son interesantes para la auditoría, pero es relativamente fácil poder marcar de alguna forma aquellos eventos que sí lo son y hacer que pasen a formar parte de la auditoría.

    Esta opción suena muy bien, pero su pega principal es que utilizar Event Sourcing introduce una complejidad muy grande en el sistema y no creo que compense introducirlo sólo para facilitar la parte de auditoría.

    Utilizar triggers en base de datos

    Es una técnica que tradicionalmente se ha usado mucho. Si estás usando una base de datos y la base de datos que estás utilizando permite crear triggers, puedes utilizarlos para detectar los cambios que se van produciendo en las tablas y actuar en consecuencia.

    Es fácil construir unos preciosos triggers genéricos que registren todos los cambios a nivel de base de datos, marcando qué usuario modifica cada vez qué tabla e incluso registrando los cambios concretos en cada tabla.

    El problema de esta aproximación es que si no haces triggers muy específicos (y te acercas entonces más al caso de la auditoría manual), la información que se va a guardar será demasiado genérica y difícilmente se pueda considerar operaciones de negocio porque tendrán poco valor semántico.

    Apoyarse en eventos del ORM

    Si subes un poco el nivel de abstracción, en lugar de utilizar triggers en base de datos puedes usar su equivalente en capa de aplicación, que serían los eventos disparados por todos los ORMs (o por la capa de acceso a datos que estés usando).

    Esta forma de auditar tiene una ventaja importante sobre hacerlo a nivel de base de datos, y es que en lugar de trabajar con tablas podemos trabajar con entidades. Eso nos aisla (parcialmente) de la estructura de datos y ayuda (probablemente) a que los eventos de auditoría que registramos tengan mayor valor de negocio.

    Aun así, sigue sin ser una auditoría tan semántica como en el caso de hacerla a mano y dependemos de que la capa de acceso a datos dispare los eventos necesarios para engancharnos y poder auditar.

    Aprovechar los puntos de entrada de un API

    Subiendo todavía más el nivel de abstracción, podemos pasar de basarnos en las entidades a basarnos en los puntos de entrada que tengamos en el API de la aplicación. No hace falta que estemos hablando de un API web, tal vez tengas una capa de servicios de aplicación, o de casos de uso, o un mediador gestionando peticiones y respuestas.

    Lo bueno de aprovechar este punto para auditar es que se supone que las acciones del API representan operaciones completas contra su tu modelo, por lo que debería corresponderse bastante bien con las acciones de negocio que quieres auditar. Puede que no todas, pero si la mayoría. Mientras que en el caso de Event Sourcing estábamos enganchándonos al resultado de las operaciones, aquí nos podemos enganchar al comando que desencadena la operación.

    Si sólo te limitas a auditar en los puntos de entrada puedes perder granularidad, por ejemplo si de una operación necesitas auditar varias cosas, pero podrías mezclar esta aproximación con la de auditoría manual que vimos al principio.

    En el caso de que tu API esté tremendamente bien diseñada, podrías incluso utilizar algún sistema declarativo para definir lo que hay que auditar en cada punto de entrada, y usar alguna técnica (decoradores, AOP o similar) para realizar la auditaría. Desgraciadamente, muchas veces no es viable automatizar tanto y la tarea es más manual de lo que nos gustaría.

    Dónde almacenar la auditoría

    Algunas de las estrategias que he comentado te llevan a almacenar la auditoría junto a los datos de la aplicación, como el caso de los triggers en base de datos, pero en muchas ocasiones en posible utilizar otros tipos de almacenamiento.

    Para elegir uno y otro entran en juego varios aspectos.

    Si la información que vas a auditar tiene una estructura poco homogénea, utilizar un sistema que permita almacenar documentos (JSON, XML, o lo que sea) puede suponer ventajas frente a una base de datos relacional.

    Sin embargo, antes de decidir usar otro sistema de persistencia para la auditoría debes tener en cuenta el nivel de transaccionalidad que necesitas. Si para ti es crítico garantizar que siempre que se produce algo en tu aplicación queda auditado, utilizar dos sistemas de persistencia diferentes te lleva al maravilloso mundo de las transacciones distribuidas, que es un mundo en el que es preferible no estar.

    Implicaciones legales

    Un último aspecto que me gustaría señalar en esto de la auditoría son las implicaciones legales que puede tener. No soy abogado y no tengo mucha idea sobre el tema, pero hay dos factores a considerar.

    En primer lugar, puede haber escenarios en los que la auditoría sea crítica desde un punto de vista normativo/legal. Si desarrollas un software para empresas de control de plagas (por poner un ejemplo), legalmente necesitas tener un control de quién ha usado qué venenos, cuándo y dónde. En estos casos necesitas tratar la auditoría como una parte fundamental del proceso de negocio y asegurarte de que se genera y almacena correctamente (cuidado entonces si tenías pensado tratarla como un subproducto almacenado en mongodb).

    Por otra parte, con las normativas de protección de datos cada vez más complejas que existen, tu sistema de auditoría debería poder permitirte eliminar toda la información relativa a un usuario. Esto, dependiendo del tipo de almacenamiento que hayas elegido, puede ser muy sencillo (sobreescribir los datos de usuario de una tabla User) o terriblemente complejo (reescribir eventos en un stream con millones de operaciones).

    Posts relacionados:

    1. ¿De quién son las herramientas de desarrollo?
    2. Cuánto daño han hecho… Los generadores de aplicaciones
    3. Cuánto daño han hecho… los arquitectos de software

    Poesía Binaria: Ejemplo para analizar y procesar expresiones matemáticas (y más) en PHP (Parsear en PHP)

    $
    0
    0

    Parsear expresiones en PHP

    Una de las mayores armas de doble filo en cuanto a los lenguajes de programación interpretados es la función o expresión eval. Esta orden nos suele permitir escribir una cadena de caracteres con un texto en el mismo lenguaje que estamos escribiendo y ejecutarla. Es decir, si nos encontramos en Python y escribimos:

    1
    eval("100+23")

    Devolverá 123. O si estamos en PHP y hacemos:

    1
    2
    <?php
    eval("echo 100+23;");

    En la cadena de caracteres que introducimos podemos utilizar variables, bucles, condiciones… vamos, todo lo que nos permite el lenguaje de programación. Lo que ponemos en la cadena se ejecutará de forma nativa en el lenguaje. Esto puede ser muy útil a veces. Pero tendremos que pensar un poco antes de utilizarlo.

    Peligros de utilizar eval

    Quiero hacer una introducción más o menos rápida y sin centrarme en un lenguaje de programación. En principio, hemos visto que eval() admite una cadena de caracteres como entrada. Si pensamos un poco, que esa cadena de caracteres sea fija, es decir, que en nuestro programa tuviera algo como los ejemplos de arriba no tiene mucho sentido, porque podemos prescindir de eval y todo se ejecutaría igual. Bueno, es cierto que en algunos casos concretos nos puede interesar la pequeña pérdida de tiempo que introduce eval(), aunque hay formas más elegantes de hacerlo. O incluso podemos hacer que el lenguaje que estemos utilizando no utilice el caché de la expresión que estamos escribiendo. Pero son, de hecho casos muy raros y excepcionales.

    Otro de los usos es construir una cadena de caracteres nosotros mismos, en tiempo de ejecución y pasárselo a eval(). En este caso, podemos sacar de una base de datos un fragmento de código para ejecutar o incluso el usuario puede introducir una expresión en un formulario y nosotros ejecutarlo. Imaginad que le pedimos al usuario una fórmula para calcular el precio de venta de un producto y claro, al usuario no vamos a pedirle programación. Eso sí, podemos tener un grave agujero de seguridad en nuestra aplicación. Sencillamente, porque eval() va a ejecutar todo lo que nosotros le pasemos. Lo mismo hace operaciones matemáticas, que llamada a cualquier función del lenguaje y ahí está el problema, al usuario no le podemos dar tanto control. Un gran poder conlleva una gran responsabilidad y, otra cosa no, pero el usuario, de responsable tiene poco y, sobre todo si estamos hablando de una plataforma web en la que cualquiera podría explotar una vulnerabilidad, mucho más.

    Incluso si se nos ocurre filtrar lo que le pasamos a eval(), acotando el número de expresiones que le pasamos y cómo se lo pasamos, no me fiaría yo mucho de que algún usuario malintencionado fuera capaz de, incluso pasando los filtros, ejecutar código malicioso. Así que, mi consejo, es que si alguna vez ejecutamos eval() sea solo para hacer pruebas, a la hora de hacer tests de nuestro programa y algún caso contado más, pero no hacerlo con código en producción.

    Expresiones del usuario

    Así que, ¿qué hacemos si aún así queremos que el usuario ejecute sus propias expresiones? No nos queda otra que analizar nosotros la expresión, y evaluarla. Como si estuviéramos programando un intérprete de un lenguaje de programación nosotros mismos. Así que, manos a la obra, vamos a construir un parser que analice una expresión y luego la procesaremos. Este programa tendrá algunas expresiones internas que evaluará directamente, aunque luego tendremos la opción de añadir funciones personalizadas.

    Atención: El código que voy a mostrar tiene ya un tiempo, aunque para escribir el post me he asegurado de que funciona con PHP7. Está basado en un código de hack.code.it de hace mucho tiempo, retocado y con algunas mejoras por mi parte.

    No pretendo crear un lenguaje de programación, solo un sistema en el que los usuarios puedan pasar de forma segura expresiones matemáticas, pueda analizarlas, evaluarlas y dar un resultado. Aunque todo se puede complicar, podemos utilizar funciones como senos, cosenos, raíces, etc, incluso funciones creadas por nosotros. Y debemos tener una manera de decirle las funciones que admitimos.

    Programando…

    Vale, voy a poner un montón de código por aquí, para tener funcionando esto. El código puede tener bugs, y, por supuesto podéis enviármelos para que poco a poco vayamos mejorando el programa. Yo lo he utilizado para unos pocos casos, pero realmente son muy pocos. El script soporta funciones, variables y operaciones como suma, resta, multiplicación, división y potencia.

    Primero, aunque no utilizaremos ningún paquete, vamos a configurar composer para generar el autoload de los ficheros:
    composer.json:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
        "name":"gasparfm/simplePHPexpr",
        "keywords":["math","expressions","functions"],
        "homepage":"https://github.com/gasparfm/simplePHPexpr",
        "description":"An expression parser in PHP",
        "license":"MIT",
        "authors":[
            {
                "name":"smassey",
                "homepage":"http://codehackit.blogspot.fr/"
            },{
                "name":"Gaspar Fernández",
                "homepage":"https://gaspar.totaki.com/"
            }
        ],
        "require":{
            "php":">=5.6"
        },
        "autoload":    {
            "psr-0":{
                "spex":"src/"
            }
        }
    }

    Crearemos un fichero principal (main.php) en el mismo directorio que composer.json. El esquema de archivos y directorios será el siguiente

    |- composer.json
    |- main.php
    |- src/
    | |-spex/
    | | |-exceptions/
    | | | |- DivisionByZeroException.php
    | | | |- MaxDepthException.php
    | | | |- OutOfScopeException.php
    | | | |- ParseTreeNotFoundException.php
    | | | |- ParsingException.php
    | | | |- UnknownFunctionException.php
    | | | |- UnknownTokenException.php
    | | |-scopes/
    | | | |- Scope.php
    | | | |- FunScope.php
    | | |- Parser.php
    | | |- Util.php

    spex/Parser.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    <?php
    namespace spex;

    /**
     * this model handles the tokenizing, the context stack functions, and
     * the parsing (token list to tree trans).
     * as well as an evaluate method which delegates to the global scopes evaluate.
     */


    class Parser {
        protected$_content=null;
        protected$_context_stack=array();
        protected$_tree=null;
        protected$_tokens=array();
        protected$_options;

        publicfunction __construct($options=array(),$content=null){
            $this->_options =$options;
            if($content){
                $this->set_content($content);
            }
        }

        /**
         * this function does some simple syntax cleaning:
         * - removes all spaces
         * - replaces '**' by '^'
         * then it runs a regex to split the contents into tokens. the set
         * of possible tokens in this case is predefined to numbers (ints of floats)
         * math operators (*, -, +, /, **, ^) and parentheses.
         */

        publicfunction tokenize(){
            $this->_content =str_replace(array("\n","\r","\t"),'',$this->_content);
            $this->_content =preg_replace('~"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"(*SKIP)(*F)|\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\'(*SKIP)(*F)|\s+~','',$this->_content);
            $this->_content =str_replace('**','^',$this->_content);
            $this->_content =str_replace('PI',(string)PI(),$this->_content);
            $this->_tokens =preg_split(
                '@([\d\.]+)|([a-zA-Z_]+\(|,|=|\+|\-|\*|/|\^|\(|\))@',
                $this->_content,
                null,
                PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
            );
            return$this;
        }

        /**
         * this is the the loop that transforms the tokens array into
         * a tree structure.
         */

        publicfunction parse(){
            # this is the global scope which will contain the entire tree
           $this->pushContext(new \spex\scopes\Scope($this->_options));
            foreach($this->_tokens as$token){
                # get the last context model from the context stack,
                # and have it handle the next token
                $this->getContext()->handleToken($token);
            }
            $this->_tree =$this->popContext();

            return$this;
        }

        publicfunction evaluate(){
            if(!$this->_tree ){
                thrownew \spex\exceptions\ParseTreeNotFoundException();
            }
            return$this->_tree->evaluate();
        }

        /*** accessors and mutators ***/

        publicfunction getTree(){
            return$this->_tree;
        }

        publicfunction setContent($content=null){
            $this->_content =$content;
            return$this;
        }

        publicfunction getTokens(){
            return$this->_tokens;
        }


        /*******************************************************
         * the context stack functions. for the stack im using
         * an array with the functions array_push, array_pop,
         * and end to push, pop, and get the current element
         * from the stack.
         *******************************************************/


        publicfunction pushContext( $context){
            array_push($this->_context_stack,$context);
            $this->getContext()->setBuilder($this);
        }

        publicfunction popContext(){
            returnarray_pop($this->_context_stack );
        }

        publicfunction getContext(){
            returnend($this->_context_stack );
        }
    }

    spex/Util.php
    Este archivo proporciona compatibilidad con PHP5. Podemos adaptar el código para PHP7 y eliminar esta dependencia.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php
    namespace spex;

    class Util {
        public static function av($arr,$key,$default=null){
            return(isset($arr[$key]))?$arr[$key]:$default;
        }

    };

    spex/scopes/Scope.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    <?php
    namespace spex\scopes;

    class Scope {
        protected$_builder=null;
        protected$_children_contexts=array();
        protected$_raw_content=array();
        protected$_operations=array();
        protected$options=array();
        protected$depth=0;

        const T_NUMBER =1;
        const T_OPERATOR =2;
        const T_SCOPE_OPEN =3;
        const T_SCOPE_CLOSE =4;
        const T_FUNC_SCOPE_OPEN =5;
        const T_SEPARATOR =6;
        const T_VARIABLE =7;
        const T_STR =8;

        publicfunction __construct(&$options,$depth=0){
            $this->options=&$options;
            if(!isset($this->options['variables']))
                $this->options['variables']=array();

            $this->depth=$depth;
            $maxdepth= \spex\Util::av($options,'maxdepth',0);
            if(($maxdepth)&&($this->depth>$maxdepth))
                thrownew \spex\exceptions\MaxDepthException($maxdepth);
        }

        publicfunction setBuilder( \spex\Parser $builder){
            $this->_builder =$builder;
        }

        publicfunction __toString(){
            returnimplode('',$this->_raw_content);
        }

        protectedfunction addOperation($operation){
            $this->_operations[]=$operation;
        }

        protectedfunction searchFunction ($functionName){
            $functions= \spex\Util::av($this->options,'functions',array());
            $func= \spex\Util::av($functions,$functionName);
            if(!$func)
                thrownew \spex\exceptions\UnknownFunctionException($functionName);

            return$func;
        }

        /**
         * handle the next token from the tokenized list. example actions
         * on a token would be to add it to the current context expression list,
         * to push a new context on the the context stack, or pop a context off the
         * stack.
         */

        publicfunction handleToken($token){
            $type=null;
            $data=array();

            if(in_array($token,array('*','/','+','-','^','=')))
                $type=self::T_OPERATOR;
            if($token==',')
                $type=self::T_SEPARATOR;
            if($token===')')
                $type=self::T_SCOPE_CLOSE;
            if($token==='(')
                $type=self::T_SCOPE_OPEN;
            if(preg_match('/^([a-zA-Z_]+)\($/',$token,$matches)){
                $data['function']=$matches[1];
                $type=self::T_FUNC_SCOPE_OPEN;
            }

            if(is_null($type)){
                if(is_numeric($token)){
                    $type=self::T_NUMBER;
                    $token=(float)$token;
                }elseif(preg_match('/^".*"$|^\'.*\'$/',$token)){
                    $type=self::T_STR;
                }elseif(preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/',$token)){
                    $type=self::T_VARIABLE;
                }else
                      echo"**".$token."**";
            }

            switch($type){
                caseself::T_NUMBER:
                caseself::T_OPERATOR:
                    $this->_operations[]=$token;
                    break;
                caseself::T_STR:
                    $delim=$token[0];
                    $this->_operations[]=str_replace('\'.$delim, $delim, substr($token, 1, -1)) ;
                    break;
                case self::T_VARIABLE:
                    $this->_operations[] = array('
    v', $token);
                    break;
                case self::T_SEPARATOR:
                    break;
                case self::T_SCOPE_OPEN:
                    $this->_builder->pushContext( new namespace\Scope($this->options, $this->depth+1) );
                break;
                case self::T_FUNC_SCOPE_OPEN:
                    $this->_builder->pushContext( new namespace\FunScope($this->options, $this->depth+1, $this->searchFunction($data['
    function'])) );
                break;
                case self::T_SCOPE_CLOSE:
                    $scope_operation = $this->_builder->popContext();
                    $new_context = $this->_builder->getContext();
                    if ( is_null( $scope_operation ) || ( ! $new_context ) ) {
                        # this means there are more closing parentheses than openning
                        throw new \spex\exceptions\OutOfScopeException();
                    }
                    $new_context->addOperation( $scope_operation );
                break;
                default:
                    throw new \spex\exceptions\UnknownTokenException($token);
                break;
            }
        }

        private function isOperation($operation) {
            return ( in_array( $operation, array('
    ^','*','/','+','-','='), true ) );
        }

        protected function setVar($var, $value) {
            $this->options['
    variables'][$var] = $value;
        }

        protected function getVar($var) {
            return \spex\Util::av($this->options['
    variables'], $var, 0);
        }

        protected function getValue($val) {
            if (is_array($val)) {
                switch (\spex\Util::av($val, 0)) {
                    case '
    v': return $this->getVar(\spex\Util::av($val, 1));
                    default:
                        throw new \spex\exceptions\UnknownValueException();
                }
            }
            return $val;
        }
        /**
         * order of operations:
         * - parentheses, these should all ready be executed before this method is called
         * - exponents, first order
         * - mult/divi, second order
         * - addi/subt, third order
         */
        protected function expressionLoop( & $operation_list ) {
            while ( list( $i, $operation ) = each ( $operation_list ) ) {
                if ( ! $this->isOperation($operation) )
                    continue;
                $left =  isset( $operation_list[ $i - 1 ] ) ? $operation_list[ $i - 1 ] : null;
                $right = isset( $operation_list[ $i + 1 ] ) ? $operation_list[ $i + 1 ] : null;

                if ( (is_array($right)) && ($right[0]=='
    v') )
                    $right = $this->getVar($right[1]);
                if ( ($operation!='
    =') && ( (is_array($left)) && ($left[0]=='v') ) )
                    $left = $this->getVar($left[1]);

                if ( is_null( $right ) ) throw new \Exception('
    syntax error');

                $first_order = ( in_array('
    ^', $operation_list, true) );
                $second_order = ( in_array('
    *', $operation_list, true ) || in_array('/', $operation_list, true ) );
                $third_order = ( in_array('
    -', $operation_list, true ) || in_array('+', $operation_list, true )|| in_array('=', $operation_list, true ) );
                $remove_sides = true;
                if ( $first_order ) {
                    switch( $operation ) {
                        case '
    ^': $operation_list[ $i ] = pow( (float)$left, (float)$right ); break;
                        default: $remove_sides = false; break;
                    }
                } elseif ( $second_order ) {
                    switch ( $operation ) {
                        case '
    *': $operation_list[ $i ] = (float)($left * $right); break;
                        case '
    /':
                            if ($right==0)
                                throw new \spex\exceptions\DivisionByZeroException();
                            $operation_list[ $i ] = (float)($left / $right); break;
                        default: $remove_sides = false; break;
                    }
                } elseif ( $third_order ) {
                    switch ( $operation ) {
                        case '
    +': $operation_list[ $i ] = (float)($left + $right);  break;
                        case '
    -': $operation_list[ $i ] = (float)($left - $right);  break;
                        case '
    =': $this->setVar($left[1], $right); $operation_list[$i]=$right; break;
                        default: $remove_sides = false; break;
                    }
                }

                if ( $remove_sides ) {
                    if (!$this->isOperation($operation_list[ $i + 1 ]))
                        unset($operation_list[ $i + 1 ]);
                    unset ($operation_list[ $i - 1 ] );
                    $operation_list = array_values( $operation_list );
                    reset( $operation_list );
                }
            }
            if ( count( $operation_list ) === 1 ) {
                $val = end($operation_list );
                return $this->getValue($val);
            }
            return $operation_list;
        }

        # order of operations:
        # - sub scopes first
        # - multiplication, division
        # - addition, subtraction
        # evaluating all the sub scopes (recursivly):
        public function evaluate() {
            foreach ( $this->_operations as $i => $operation ) {
                if ( is_object( $operation ) ) {
                    $this->_operations[ $i ] = $operation->evaluate();
                }
            }

            $operation_list = $this->_operations;

            while ( true ) {
                $operation_check = $operation_list;
                $result = $this->expressionLoop( $operation_list );

                if ( $result !== false ) return $result;
                if ( $operation_check === $operation_list ) {
                    break;
                } else {
                    $operation_list = array_values( $operation_list );
                    reset( $operation_list );
                }
            }
            throw new \Exception('
    failed... here');
        }
    }

    spex/scopes/FunScope.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?php
    namespace spex\scopes;

    class FunScope extendsnamespace\Scope {
        private$fun=null;

        publicfunction __construct(&$options,$depth,$callable){
            parent::__construct($options,$depth);
            $this->fun=$callable;
        }

        publicfunction evaluate(){
            $arguments= parent::evaluate();
            returncall_user_func_array($this->fun,(is_array($arguments))?$arguments:array($arguments));
        }
    }

    spex/exceptions/UnknownFunctionException.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php

    namespace spex\exceptions;

    class UnknownFunctionException extends \Exception
    {
        function __construct($functionName){
            parent::__construct('Unkown function '.$functionName);
        }
    }

    spex/exceptions/DivisionByZeroException.php
    Todos los archivos de excecpción serán iguales, cambiando el nombre, el objetivo es diferenciar las excepciones para poder capturarlas.

    1
    2
    3
    4
    5
    6
    7
    <?php

    namespace spex\exceptions;

    class DivisionByZeroException extends \Exception
    {
    }

    main.php:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <?php
    include('vendor/autoload.php');

    $config=array(
        'maxdepth'=>2,
        'functions'=>array(
            'sin'=>function($rads){returnsin(deg2rad($rads));},
            'upper'=>function($str){returnstrtoupper($str);},
            'fact'=>function($n){$r=1;for($i=2;$i<=$n;++$i)$r*=$i;return$r;},
            'word'=>function($text,$nword){$words=explode(' ',$text);return(isset($words[$nword]))?$words[$nword]:'';},
        )
    );

    $builder=new \spex\Parser($config);

    while((fputs(STDOUT,'math > '))&&$e=fgets(STDIN)){
        if(!($e=trim($e)))continue;
        if(in_array($e,array('quit','exit',':q')))break;

        try {
            $result=$builder->setContent($e)->tokenize()->parse()->evaluate();
        } catch ( \spex\exceptions\UnknownTokenException $exception){
            echo'unknown token exception thrown in expression: ',$e, PHP_EOL;
            echo'token: "',$exception->getMessage(),'"',PHP_EOL;
            continue;
        } catch ( \spex\exceptions\ParseTreeNotFoundException $exception){
            echo'parse tree not found (missing content): ',$e, PHP_EOL;
            continue;
        } catch ( \spex\exceptions\OutOfScopeException $exception){
            echo'out of scope exception thrown in: ',$e, PHP_EOL;
            echo'you should probably count your parentheses', PHP_EOL;
            continue;
        } catch ( \spex\exceptions\DivisionByZeroException $exception){
            echo'division by zero exception thrown in: ',$e, PHP_EOL;
            continue;
        } catch ( \Exception $exception){
          echo'exception thrown in ',$e, PHP_EOL;
          echo$exception->getMessage(), PHP_EOL;
          continue;
        }

        echo$result, PHP_EOL;
    }

    Probando el programa

    Tras todo esto, podemos hacer una ejecución como esta:

    php main.php
    1+1
    2
    upper(“hola mundo”)
    HOLA MUNDO
    fact(10)
    3628800
    word(“Hola Mundo Mundial”, 1)
    Mundo
    sin(fact(3))
    0.10452846326765

    Publicación del código

    Quiero publicar en GitHub el código tal y como ha quedado con algunos ejemplos prácticos más en las próximas semanas.

    Foto principal: unsplash-logoChris Liverani

    The post Ejemplo para analizar y procesar expresiones matemáticas (y más) en PHP (Parsear en PHP) appeared first on Poesía Binaria.

    Variable not found: Enlaces interesantes 323

    $
    0
    0
    Enlaces interesantesAhí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

    .NET / .NET Core

    ASP.NET / ASP.NET Core

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

    Data

    HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publicado en Variable not found.
    Viewing all 2713 articles
    Browse latest View live