En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

domingo, 6 de noviembre de 2016

El gran lighttpd
cómo escribir un módulo lighttpd en C - pt.3

    El Nota: ¿Seguro que no le importará? 
    Bunny: A él no le importa nada. Es un nihilista.     
    El Nota: Eso debe ser agotador.

, un poco agotados hemos llegado al último capítulo, hoy vamos a escribir el código de nuestro mini módulo lighttpd. Vaya, muy agotador para El Nota...
...estoy agotado...
Pero antes veamos un momento cómo está estructurado un módulo. Aquí hay una pequeño esquema descriptivo (extracto de nuestro mod_helloworld.c creado en el post anterior: ¡volver inmediatamente a leer el post anterior!):
// init the plugin data
INIT_FUNC(mod_helloworld_init) {
    ...
}

// destroy the plugin data
FREE_FUNC(mod_helloworld_free) {
    ...
}

// handle plugin config and check values
SETDEFAULTS_FUNC(mod_helloworld_set_defaults) {
    ...
}

// handle uri
URIHANDLER_FUNC(mod_helloworld_uri_handler) {
    ...
}

// this function is called at dlopen() time and inits the callbacks
int mod_helloworld_plugin_init(plugin *p) {
    p->version          = LIGHTTPD_VERSION_ID;
    p->name             = buffer_init_string("helloworld");
    p->init             = mod_helloworld_init;
    p->handle_uri_clean = mod_helloworld_uri_handler;
    p->set_defaults     = mod_helloworld_set_defaults;
    p->cleanup          = mod_helloworld_free;
    p->data             = NULL;
    return 0;
}
Se puede notar el uso de unas macros (amablemente proporcionadas por el entorno de desarrollo lighttpd) para crear unas callback con las que inicializar una estructura plugin que luego lighttpd utilizará para gestionar el nuestro módulo cada vez que una petición HTTP pida de usarlo. Para aquellos que quieran profundizar el argumento callback ya ha sido ampliamente descrito aquí y aquí (supongo que ya habéis leído esos post... ¿o no?).

Entonces, ¿qué partes de nuestro módulo tenemos que cambiar para que, usándolo, nos escriba "Hello, world!" en el navegador? Dado que el problema es bastante simple, también el código no será muy largo y complejo: sólo tendremos que cambiar la callback mod_helloworld_uri_handler  de la siguiente manera:
URIHANDLER_FUNC(mod_helloworld_uri_handler)
{
    plugin_data *p = p_d;
    UNUSED(srv);

    // test modo (return si error)
    if (con->mode != DIRECT)
        return HANDLER_GO_ON;

    // test uri path (return si error)
    if (con->uri.path->used == 0)
        return HANDLER_GO_ON;

    mod_helloworld_patch_connection(srv, con, p);

    // test handler (return si error)
    if (con->uri.path->ptr && strstr(con->uri.path->ptr, "helloworld")) {
        // escribe buffer
        buffer *b = chunkqueue_get_append_buffer(con->write_queue);
        BUFFER_APPEND_STRING_CONST(b, "<big>Hello, world!</big>");

        // send header
        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
        con->http_status = 200;
        con->file_finished = 1;

        // handling terminado
        return HANDLER_FINISHED;
    }
    else
        return HANDLER_GO_ON;
}
Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciones y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.

Algunas partes están idénticas a la callback original (pero he añadido algunos comentarios), y son típicas de la estructura del código lighttpd: por no complicar el asunto no hablaré de estas partes. Sin embargo, podéis confiar en ellas porque lighttpd está escrito y funciona como debería.

La parte realmente nueva che he escrito es la que empieza con el comentario "test handler (return si error)", donde se testea si el URI pasado al navegador contiene el nombre del módulo y, en ese caso, ejecuta lo que queremos: copia en un buffer el texto "Hello, world!" y lo envía como respuesta a la petición. Así que si escribimos en la barra de direcciones de Firefox, Chrome, Opera (o cualquier otro navegador menos IE, por favor...)
http://127.0.0.1/helloworld
tendremo como respuesta en el browser:
Hello, world!
He escrito 127.0.0.1 (y se hubiera podido escribir localhost) porque, por supuesto, la primera prueba la haremos de forma local. Si luego queréis abrir vuestro equipo hacia el mundo exterior y utilizarlo como un Web Server real (en una red local o Internet) podéis repetir el test usando la vuestra dirección IP pública y obtendréis el mismo resultado. ¡El módulo funciona!

El código que acabo de enseñar lo he escrito y probado con lighttpd 1.4.33. Si utilizáis una versión más reciente (de la 1.4.36 en adelante) descubriréis que algo ha cambiado a nivel de gestión de buffer, por lo que, por ejemplo, las líneas:  
if (con->uri.path->used == 0)
...
buffer *buf = chunkqueue_get_append_buffer(con->write_queue);
tienen que cambiar en:   
if (buffer_is_empty(con->uri.path))
...
buffer *buf = buffer_init();
pero estos son detalles. En futuro, según lo prometido, os propondré algún módulo más sofisticado, con cambios también en las otras callback, pero esto es suficiente por el momento, no quiero decir más de la cuenta, si no nos cansaremos como El Nota...

¡Hasta el próximo post!