Out Of Bounds Exception!

Mi lugar de irreflexión y algo de programación

Archivos en la Categoría: Programación

Codeando un servidor web en python para administrar programas remotamente (y III)

Bueno, últimamente he andado algo liado con trabajos, terminar la carrera, PFC y demás, así que no he podido dedicar nada de tiempo a mi querido servidor en python.

No obstante he subido el proyecto a Google Code para manejarlo desde ahí, así que ya que estoy pongo la url para los interesados. También lo añado a la sección de descargas

http://code.google.com/p/remote-process-management/

Si alguien está interesado en participar que me envíe un mail y tan ricamente le doy permisos.

PD: Perdón por las chapucillas, un día de estos lo estructuro todo bien y todo 😀

Un saludo.

Codeando un servidor web en python para administrar programas remotamente (II)

Por si te perdiste la parte 1.

Bueno, voy a continuar con el programa que empezamos en el post pasado. Lo que habría que hacer a continuación es crear una clase para el manejo de procesos y añadir diversos métodos a nuestra clase principal para controlarlos. Empecemos con la nueva clase:

En mi caso se va a llamar ProcessManager (muy original, ¿eh?) y va a tener una clase interna para manejar la entrada/salida del processo con un thread:

class ProcessManager:
    class ProcessReaderThread ( threading.Thread ):
    
        def __init__ ( self, subprocess):
            self.proc = subprocess
            self.alive = True
            self.buffer = list()
            threading.Thread.__init__ ( self )
    
        def run ( self ):
            while self.alive:
                output = self.proc.stdout.readline().strip()                
                self.buffer.append(output)
                
        def stop (self):
            self.alive = False
            
        def tell (self, cmd):
            self.proc.stdin.write('%s\n' % cmd)
            self.buffer.append(cmd)            

El constructor acepta un objeto subprocess (que veremos a continuación), inicializa el buffer de salida y crea la variable alive que determinará cuándo un proceso está o no en ejecución.
El método run se ejecuta para arrancar nuestro thread y lee la salida estándar del proceso mientras que alive sea true.
El método stop hace que alive sea false por lo que ya no se leerá más de la salida del proceso.
El método tell pasa un comando a la entrada estándar del proceso.

Además de esta clase interna, ProcessManager tendrá los siguientes métodos:

    def __init__ (self, name, path, processName):
        self.name = name
        self.path = path
        self.processName = processName
        self.process = None
        self.thread = None

Método constructor de la clase que toma como parámetros el nombre, ruta y proceso que sacamos de nuestro fichero de programas. Las variables process y thread se (auto)explican ahora.

El método startProcess iniciará el proceso en cuestión:

    def startProcess(self):
        self.process = subprocess.Popen(self.path,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                )
        
        #Start a different thread to read process's output
        self.thread = self.ProcessReaderThread(self.process, self.log)
        self.thread.start()

Básicamente utilizamos la clase subprocess para abrir el proceso indicándole que tanto la entrada como la salida estándar las administraremos nosotros. Posteriormente creamos un nuevo thread y le pasamos este descriptor de proceso para que se encargue de él. Por tanto las variables process y thread servirán para controlar tanto el thread “lector/escritor” como el proceso en sí.

Obviamente, como “todo lo que tiene un principio, tiene un final Neo“:

    def killProcess(self):
        self.thread.stop()
        try:
            self.process.terminate()
        except:
            pass

Este método símplemente dirá al thread que deje de leer/escribir en el proceso y después terminará éste.

Nuestro método tell servirá para que la clase principal del programa lance los comandos que quiera que reciba el proceso

    def tellProcess(self, cmd):
        try:            
            self.thread.tell(cmd)
        except:
            pass

Básicamente llamará a la función ya vista de la clase interna. Nota: aquí hay un pequeño bug (solucionado en la versión que pondré para descarga) por el cual, si el proceso es cerrado sin llamar al método de esta clase (por ej: haciendo un exit si el programa es un terminal) al intentar ejecutar un comando, saltará una excepción, por lo tanto habría que comprobar si el proceso de verdad está corriendo y, si no, terminarlo.

Finalmente nos quedan un par de métodos básicos:

    def isRunning(self):
        if self.thread:
            return self.thread.alive
        
        return False
    
    def getOutputBuffer(self):
            return self.thread.buffer

Y con esto ya tendríamos nuestro manejador de procesos. A continuación pongo los nuevos métodos que tendrá nuestra clase principal para lidiar con todo esto y también haremos que nuestro servidor acepte peticiones POST para que se pueda manejar desde fuera.

Empezamos con las peticiones POST en la clase ServerHandler

    def do_POST(self):
        try:            
            form = cgi.FieldStorage(
            fp = self.rfile, 
            headers = self.headers,
            environ = {'REQUEST_METHOD':'POST',
                       'CONTENT_TYPE':self.headers['Content-Type'],
                      })
                    
            if len(form.keys()) > 0:
                field = form.keys()[0]
                action = field.split("_")[0]
                name = field.split("_")[1]
                        
                if action == 'cmd':
                    if len(form[field].value) > MAX_CMD_LENGTH:
                        self.log_alert("%s" % OVERFLOW_ATTEMPT, len(form[field].value))
                    executeCommand(name, form[field].value[:MAX_CMD_LENGTH], self.client_address[0])
                elif action == 'switch':
                    switchProgram(name)
                            
                self.send_response(200)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                self.wfile.write(makeHtml())
                return
            
        except Exception, err :
            if DEBUG: print 'Exception in POST: ' + str(err)
            self.send_error(500)        
            return 

Dado que nuestro html es muy listo y en el valor de los botones se incluye el nombre del proceso, nos bastará con coger los parámetros POST suministrados (con el cgi.FieldStorage) y hacer un split para tener la acción a realizar y el nombre del proceso sobre el cual hacerlo.
Una vez tengamos esto diferenciaremos las dos opciones actuales (switch* para cambiar de ON a OFF y viceversa; cmd para ejecutar un comando) y llamaremos a uno u otro método.
*Nótese que cometí el error de poner dos botones y hacer un switch, lo cual no tiene sentido, pero lo arreglaré en la siguiente versión.

Vamos ahora con el método de switch (que más tarde tendrá que aceptar un parámetro para ver si queremos encenderlo o apagarlo):

def switchProgram(name):
    
    global programs
    for program in programs:
        if program['name'] == name:
            #Create a new manager if there's noone
            if not program['manager']:
                program['manager'] = ProcessManager(program['name'], program['path'], program['process'])
                
            if not program['manager'].isRunning():
                if DEBUG: print "Turning ON " + name 
                program['manager'].startProcess()
            else:
                if DEBUG: print "Turning OFF " + name 
                program['manager'].killProcess()
                
            break

Básicamente cogemos nuestra lista de programas y buscamos el que nos piden por parámetro (quizá se podría agilizar esto usando un diccionario, así que lo apuntaré para futuras revisiones). Una vez encontrado miraremos a ver si alguna vez durante la ejecución hemos creado un manager para él (si no, lo creamos), y llamaremos a startProcess o killProcess dependiendo de si está apagado o encendido respectivamente.

Finalmente nuestro método executeCommand hará lo siguiente:

def executeCommand(name, command):
    
    global programs
    
    for program in programs:
        if program['name'] == name and program['manager']:
            program['manager'].tellProcess(command)

Buscamos el programa que nos piden y, si tiene un manager, le pasamos el comando.

Y ya está, más o menos por encima he explicado como crear un programa de unas 400 loc para administrar nuestros procesos. He obviado algunas partes porque se meten más con html que con el programa en sí, pero realmente son muy sencillas (quizá tediosas) de hacer.

Esto sería una versión alpha si acaso, aún necesitaríamos añadir logs, autenticación, protección contra XSS, lista de baneados, un nombre decente y algunas mejoras más que ya iré viendo y posteando según las vaya incluyendo. El programa lo pondré para descarga cuando considere que está medianamente presentable (está hecho en dos tardes, así que imaginad).

Un saludo.

Codeando un servidor web en python para administrar programas remotamente (I)

Tras una temporadilla considerable sin pasarme por estos lares a actualizar con alguna aventura de las mías, hoy, por fin, vuelvo a las andadas.

Y lo hago nada más y nada menos que con un programa que se me ocurrió en una de esas tardes ociosas típicas: un proceso que actúe como servidor web, mostrando una página html pseudodinámica (luego explico por qué el ‘pseudo’) que permita a un usuario administrar (iniciar, detener y comunicarse) una serie de programas configurables.

La idea me vino debido a que mis compañeros de clase y yo solemos jugar a diversos juegos según la temporada, y muchas veces a uno le toca hacer de servidor para alguno en concreto, por lo que se hace algo pesado estar pendiente de cuando los otros quieren jugar y tú no quieres o no estás. Por lo tanto, creé este programa para evitarme eso, símplemente me basta con tener el pc encendido conectado a internet y el servidorcillo web corriendo.

Tras esta pequeña introducción, vamos al lío. Primero hay que elegir el lenguaje de programación, el cual tiene que cumplir, al menos, dos requisitos: mínimo consumo de memoria y facilidad para lo que queremos hacer. Expongo a continuación mis 3 lenguajes preferidos y vamos descartando:

C++ no es precisamente trivial para abrir sockets ni en el manejo de procesos sobre Windows, por no hablar de que la programación sobre esta plataforma, para mí, es horrible, así que ni me lo planteo.
Java y su consumo de memoria no es precisamente óptimo y, aunque probablemente se pueda configurar la máquina virtual para que no ocupe esos odiosos 50 MB de ram para hacer una suma, quiero algo simple y lo quiero ya, así que descartado.
Python… qué puedo decir de mi amado Python, es más simple que el mecanismo de un chupete, consume poco más que un programa en C y además es posible que el resultado sea portable a Linux y todo. No se hable más.

¡Y empieza el codeo! Empecemos con la clase principal, que incluye el sevidor (nótese que soy algo nuevo en Python, así que es probable que muchas cosas se puedan hacer mejor o más rápido; si es así decídmelo y lo actualizo con mucho gusto).

Función main que es la que arranca todo el tema (llamada cuando se arranca el script):

def main():
    try:
        print 'Programs loaded...'
        loadProgramList()
        server = HTTPServer(('', 8080), ServerHandler)
        print 'Started httpserver...'
        server.serve_forever()
    except KeyboardInterrupt:
        print '^C received, shutting down server'
        server.socket.close()

if __name__ == '__main__':
    main()

Carga la lista de programas y arranca el servidor, simple, ¿no?

La función loadProgramList símplemente va a abrir un archivo de programas que tiene el siguiente formato (nótese que cada campo está separado por una tabulación, no por un espacio):

#NOMBRE RUTA PROCESO DESC
TaskManager V:\Window$\System23\taskmgr.exe taskmgr Programa bonito

La función es la que sigue:

def loadProgramList():
    file = open('programs')
    
    global programs
    programs = list()
        
    for line in file:
        if line.find('#') != 0 and line != '':
            line = line.replace('\n','').split("\t")
            programs.append({'name':line[0], 'path':line[1], 'process':line[2], 'manager':None})
            
    return programs

Programs es una variable global en la que guardamos una lista donde cada programa es un diccionario clave-valor con las propiedades extraídas del fichero y un extra, manager, que luego explicaré.

Vamos a la clase del ServerHandler:

class ServerHandler(BaseHTTPRequestHandler):
    
    def do_GET(self):
        try:
            if not self.path.endswith('favicon.ico'):                
                                   
                self.send_response(200)
                self.send_header('Content-type', 'text/html')
                self.end_headers()
                self.wfile.write(makeHtml())                    
                return
                
        except IOError:
            self.send_error(404,'File Not Found: %s' % self.path)
            return     

Algo simplificado, pero igualmente válido para explicar las bases. Cuando nos soliciten una página por get, contestamos con el resultado de la función makeHtml

Para terminar con la primera parte, veamos cómo es esta función:

def makeHtml():
    html = ''
    
    file = open('index.html')
    
    #load the html
    for line in file:
        html += line
            
    #find server's tags
    regex = re.compile(r' .*? ')
    tags = regex.findall(html)
    
    #replace server's tags
    for tag in tags:
        keyword = tag.replace('', '').replace('', '').strip()
        if keyword == ...
            content = ...
            html = html.replace(tag, content)
    return html

Vamos por partes, primero abrimos la página en html que hemos creado (con su css, javascript y demás ya hecho) y buscamos nuestras etiquetas personalizadas, que tendrán la forma <py>tag</py> donde ‘tag’ estará definido en el código (o, en caso de que en un futuro se quiera expandir esto, en un fichero). Ahora se sustituye cada una de las etiquetas por un contenido concreto, en mi caso haré que por cada programa se cree una etiqueta <li> para mostrar una serie de pestañas hechas con JQuery. Por otro lado debajo de esto añadiré, también para cada programa, un contenido concreto (varios botones, una caja de texto, etc) para el manejo de cada programa. En resumen, queda algo así:

El estilo ya es cosa de cada uno, yo como soy algo cutre lo he hecho lo más simple posible =P

En la parte 2 añadiré algunas mejoras y veremos cómo arrancar procesos y comunicarnos con ellos.

¡Un saludo!

El lío con Java y Bash

Me considero un Windowsero (no confundir con fanboy) de toda la vida, de hecho hasta hace apenas uno o dos años no sabía ni que existía Linux (qué le vamos a hacer) y cuando me lo presentaron obviamente no me gustaba: una interfaz “cutrilla”, mucho que teclear en la consola, dificultad para instalar/desinstalar cosas, pocos juegos (xD) y todas esas cosillas que un usuario de Windows dice (que no tienen por qué ser ciertas).

No obstante, una vez que le fui cogiendo el truco, me ha llegado a gustar y, a pesar de que no lo uso como SO principal, sí lo hago para trabajar, hacer el PFC o para trastear con temas de seguridad, scripting, etc. Es cuestión de gustos, para unas cosas me es más útil Windows y para otras Linux =) (no me voy a meter en para qué cosas es mejor uno u otro, cada cual que les saque el partido que quiera/pueda).

El caso es que últimamente estoy trasteando en un servidor Ubuntu con Tomcat (a.k.a. Servlets en Java) al que accedo mediante Kitty (un fork del famoso Putty), desde donde me dedico principalmente a toquetear una base de datos Mysql y a mirar algunos logs, nada del otro mundo.

Hace poco sacamos una nueva tarea en el scrum del proyecto que consistía en, dado un fichero multimedia (audio o vídeo), sacar la duración del mismo en el servidor. Como ya se usa ffmpeg en otra parte del servidor, decidí ver qué tal se haría así en vez de mediante Java (ya que para ello tengo entendido que se requieren librerías externas). Por tanto mi primera tarea era ver cómo me daba este valor el programa, lo cual es bastante simple (lo voy a mostrar paso a paso de cómo lo he ido haciendo, por lo que es normal que algún comando del principio no funcione xD):

ffmpeg -i video.3gp

Lo cual nos devuelve este tochaco:

ffmpeg version 0.8.git, Copyright (c) 2000-2011 the FFmpeg developers
  built on Jul 21 2011 08:29:21 with gcc 4.3.2
  configuration: --disable-yasm
  libavutil    51. 11. 0 / 51. 11. 0
  libavcodec   53.  8. 0 / 53.  8. 0
  libavformat  53.  6. 0 / 53.  6. 0
  libavdevice  53.  2. 0 / 53.  2. 0
  libavfilter   2. 27. 0 /  2. 27. 0
  libswscale    2.  0. 0 /  2.  0. 0
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x9ec4380] max_analyze_duration 5000000 reached at 5046000
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'ruta/video.3gp':
  Metadata:
    major_brand     : 3gp4
    minor_version   : 768
    compatible_brands: 3gp4mp413gp6
    copyright       :
    copyright-eng   :
  Duration: 00:00:05.28, start: 0.000000, bitrate: 91 kb/s
    Stream #0.0(eng): Video: mpeg4, yuv420p, 176x144 [PAR 1:1 DAR 11:9], 74 kb/s, 19.10 fps, 30 tbr, 1k tbn, 30 tbc
    Stream #0.1(eng): Audio: amrnb, 8000 Hz, 1 channels, flt, 12 kb/s
    Stream #0.2(eng): Data: mp4s / 0x7334706D, 0 kb/s
    Stream #0.3(eng): Data: mp4s / 0x7334706D, 0 kb/s
At least one output file must be specified

Donde se puede observar también que nos da un pequeño error en la última línea, dado que no le hemos especificado el archivo de salida ya que no nos interesa al querer extraer su información (si no, nos sobrescribiría el fichero o crearía un archivo igual que luego tendríamos que borrar). Pero bueno, al menos tenemos la duración, así que sólo falta extraerla del tochaco ese, por lo que vamos probando con algo simple:

ffmpeg -i video.3gp | grep Duration

Pero la sorpresa está en que no ocurre nada, vuelve a salir todo el chorrazo otra vez… Después de un rato devanándome los sesos descubro que el problema está en que la salida la hace por stderr, por lo que el pipe no lo coge como entrada, así que Googleando un poco encuentro un arreglo:

ffmpeg -i video.3gp 2>&1 | grep Duration

Que básicamente redirige la salida de error (2>) a la salida estándar (&1). Y, ahora sí, nos devuelve la línea en cuestión:

  Duration: 00:00:05.28, start: 0.000000, bitrate: 91 kb/s

Continuando un poco y encadenando algún comando más nos sale algo como esto:

ffmpeg -i video.3gp 2>&1 | grep Duration | awk '{print $2}' | sed 's/.\{4\}$//'

Que básicamente lo que hace es coger el segundo campo con awk y quitar los 4 últimos caracteres correspondientes al punto, los centisegundos y la coma separadora.

Si se prefiere no usar tantos pipes, con awk se llega a la misma solución (aunque se podría mejorar la expresión regular a algo como esto (\d\d:){2}\d\d, suponiendo que soporte el \d para dígitos y el {N} para repeticiones):

ffmpeg -i $1 2>&1 | awk '/Duration: [0-9][0-9]:[0-9][0-9]:[0-9[0-9]/ {print substr($2,1,8)}'

Pues qué bonito todo, la vida nos sonríe y esto ya está (o no ;)), sólo falta metérselo a Java para que obre su magia, básicamente algo así:

String command = "ffmpeg -i " + file.getAbsolutePath() + " 2>&1 | grep Duration | awk '{print $2}' | sed 's/.\\{4\\}$//'";
Process pr = Runtime.getRuntime().exec(command);

Posteriormente habría que coger el InputStream de nuestro objeto pr y leer de ahí (sí, es InputStream y no OutputStream, es que son unos cachondos =])

El problema (porque obviamente iba a haber más de uno) es que esto no funciona y el por qué, ahora obvio para mí después de pelearme con ello, es el siguiente: al llamar al método exec(comando) estamos ejecutando el programa del comando en cuestión (ffmpeg en este caso) y, dado que los pipes son propios de Linux y no de este programa, obviamente esto no puede funcionar. Por tanto una aproximación más válida sería algo como esto:

"bash -c \"ffmpeg -i " + file.getAbsolutePath() + " 2>&1 | grep Duration | awk '{print $2}' | sed 's/.\\{4\\}$//'\"";

Que básicamente ejecuta una shell con esos comandos donde los pipes si estarían permitidos. Aún así, nos suelta un error más:

-i: -c: line 0: unexpected EOF while looking for matching `"'
-i: -c: line 1: syntax error: unexpected end of file

Que probablemente se deba al lío de comillas, barras normales e invertidas y demás que tenemos ahí… Con lo cual aquí se me iluminó la bombilla y decidí que iba a optar por algo mejor: un script. Me parece una solución mucho mejor por varios motivos:

  • Es optimizable: si nuestro ejecutable (ffmpeg) se actualiza y hay que cambiar los comandos, encontramos otro mejor o símplemente vemos una solución más óptima basta con modificar el script sin modificar la entrada o salida de datos.
  • Es reutilizable: Puede que nos sirva para un uso particular o para otros proyectos más adelante.
  • Es cómodo: no tendremos que estar cogiendo el código del servidor y ejecutándolo para probar cosas, ya que basta con ejecutar el script y voilá. Asimismo por cada modificación no hay que estar haciendo deploy del servidor y demás…

La “desventaja” que veo sería la portabilidad, ya que habría que mover el script con el servidor en caso de que se despliegue en otro sitio. Además habría que vigilar la posibilidad de que fuese eliminado, de modo que se volviese a generar o al menos que no causase alguna excepción graciosa.

Y bueno, hasta aquí mi experiencia de estos días con Linux y Java, al final lo hice funcionar (a pesar de subestimar la tarea) y me he quedado a gusto =P

Agur!

Los códigos WTF

En la vida de todo programador (ya sea Informático, Teleco, de FP o mi abuela dándole a la tecla) llega un momento en el que uno se embarca (o le embarcan) en algún tipo de proyecto más o menos grande en el que, inevitablemente, va a tener que reutilizar código ajeno y/o trabajar con otros compañeros. También existe la posibilidad de encontrarte esta situación en códigos ajenos que encuentras por ahí, ya sean tutoriales, ejemplos, PoCs (pruebas de concepto), como porno escrito en C… Aunque con estos últimos (los publicados en Internet, no el porno) es bastante menos común, ya que normalmente uno se lo piensa un par de veces antes de publicar algo, ¿no? =P

Se cumpla una, otra o las circunstancias que sean, el caso es que aparece cerca ese momento en el que te va a tocar modificar el código de otro (o no) y, muy probablemente, comprender qué hace (esto normalmente sí), y es en este momento en el que la tensión crece, la piel se te pone de gallina y rezas a los dioses que conoces para que no te toque a ti…

A mí me tocó hace poco, así que decidí recopilar unos cuantos más que fuese viendo y hacer una entrada sobre ello, que para algo el blog es mío 😀

  • El primero es un código en C que encontré en un ejemplo de inyección de código en procesos:
    BOOL st=TRUE;
    do
    {
        if(strcmp(pInfo.szExeFile, "test.exe")==0)
        {
            myPid=pInfo.th32ProcessID;
            break;
        }
        Process32Next(processList, &amp;pInfo);
    }
    while(st!=FALSE);

    Obviando el hecho de que se le colaron algunos códigos HTML (incluido algún “&lt;” y “&gt;” en los include), el problema de este código es evidente: ¿para qué declarar un booleano que no usas?. Eso sin entrar a si es más o menos “elegante” el uso del break.

  • Este siguiente está en Java y es algo curioso:
    File f = new File (filename);
    if (f =! null){
        //...
    }

    Según mi parecer algunos programadores nos solemos poner algo paranoicos cuando llevamos muchas líneas de código escritas en un proyecto que no termina de funcionar, por lo que acabamos comprobando hasta lo más obvio, ¿cierto?

  • Más Java, este es de un tutorial de una librería con la que me he estado peleando últimamente:
    if(true) {
        //...
    }
    else {
        //...
    }

    Realmente no tengo nada que decir… podría entender que alguien escriba un código así si programa sobre algún IDE muy cutre o incluso con un simple editor de textos y, además, el proyecto en cuestión ha ido creciendo y esto lo ha implementado en varias fases. No obstante las risas te las echas igualmente cuando lo ves 🙂

  • Este es una aportación de mi amigo ccdg:
    public boolean containsStation(String station) {
        return stations == null ? null : stations.containsKey(station);
    }

    Este sí que es un verdadero código para decir “WTF??” ya que técnicamente no es posible convertir un null a un tipo de dato (ya sea boolean, int, float, o cualquiera, aunque sí a los objetos que los representan, como Boolean, Integer, etc.), no obstante parece ser que este programador se las ha apañado para “engañar” al compilador de Java y hacer que no detecte esta incompatibilidad de equipos, por lo que un código como el siguiente compilaría perfectamente, solo que tiraría un NullPointerException al ejecutarse:

    boolean a = true ? null : false;

    De hecho es curioso ver cómo un IDE como Eclipse nos señalará que “false” es código muerto, ya que siempre se va a cumplir que “true” es true, pero no se percatará de la asignación a no ser que sea demasiado obvia:

    boolean a = true ? null : null;

Y esto es todo amigos, espero que les hayan gustado y que no repitan ninguno en su trabajo =P