Escribir software es un placer. Un programa no sólo debe funcionar bien y ser eficiente (esto se da por supuesto), sino que también debe ser bello y elegante de leer, comprensible y fácil de mantener, tanto para el autor como para eventuales futuros lectores. Programar bien en C es un arte.
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...
viernes, 23 de diciembre de 2016
jueves, 8 de diciembre de 2016
El último Apache III - La venganza cómo escribir un módulo Apache en C - pt.3
Entonces, hacemos el punto: en los últimos tres post hemos hablado de módulos lighttpd (aquí, aquí y también aquí). En el primero de los tres he mencionado que el tema no era nuevo: antiguamente había escrito dos post (aquí y aquí) que hablaban de módulos para Apache, que es un pariente cercano de lighttpd (familia Web Servers). Aquí, para cerrar el circulo os propongo una tercera entrega de "El último Apache" (¿o Mohicano?), con una (espero) interesante extensión de lo que se había escrito.
Pues bien, en "El último Apache II - El regreso" (que acabáis de releer, supongo...) escribimos un agradable módulo elemental para Apache. Hacia poco (escribía solo una presentación en browser), pero era un buen comienzo para entrar en el mundo de los módulos para Web Servers. Vale, ahora vamos a reanudar ese módulo y les vamos a añadir una función que extrae los datos POST incluidos en una eventual petición HTTP de tipo POST que llega al módulo. ¿Por qué he elegido añadir esa funcionalidad? Bueno, obviamente porque es bastante normal que un módulo cuente con varios tipos de peticiones (GET, POST, etc.), y luego, en particular, recuerdo que la primera vez que he añadido esta funcionalidad en un módulo Apache que escribí, me di cuenta de que no estaban disponibles muchas indicaciones sobre este tema (y esto a pesar de la enorme cantidad de documentación de Apache disponible respeto a la de lighttpd).
Pues bien, sin molestarse en repetir todo el código y la forma de generarlo (hay de todo en "El último Apache II - El regreso"), sólo reescribiremos la función myapmodHandler() y añadiremos una nueva función getPost(). ¡Vamos con el código!
la función original myapmodHandler() se ha convertido en una especie de función helloworld (bueno, en práctica lo era incluso antes), que distingue los tipos de petición y, en el caso POST, llama a la función readPost() y escribe como respuesta "Hello, world!" mas los datos. En el caso de peticiones GET o POST sin datos se limita a escribir "Hola, mundo!", y para otros tipos de peticiones sale con error.
La función readPost() es simple, pero no es inmediata: en su tiempo la derivé de el único ejemplo interesante y bien hecho que encontré, y que estaba en el (óptimo) libro "Writing Apache Modules with Perl and C", que os recomiendo. Yo diría que en la última versión de Apache, la 2.4, se han introducido (y descritos aquí) otros métodos de extracción de datos de POST (¡os deseo buena lectura!), de todas maneras yo me la arreglé así, y el módulo funcionaba (y funciona todavía) muy bien.
¡Ah, el test, se me olvidaba! Testear con peticiones POST no es simple cómo hacerlo con las GET, adonde es suficiente (como dicho en el post anterior) escribir la URI del modulo en la barra de direcciones de Firefox o Chrome y esperar la respuesta. Para probar el nuestro nuevo módulo es mejor usar un buen plugin (como Poster, por ejemplo) que nos facilitará mucho la tarea.
Vale, de momento, creo que podemos parar por un tiempo el tema módulos de Apache/lighttpd. Juro que en el próximo post voy a hablar de otra cosa, no me gustaría que pensaran que el C se utiliza sólo para Web Servers...
¡Hasta el próximo post!
![]() |
| ...en realidad soy un Mohicano, no un Apache... |
Pues bien, sin molestarse en repetir todo el código y la forma de generarlo (hay de todo en "El último Apache II - El regreso"), sólo reescribiremos la función myapmodHandler() y añadiremos una nueva función getPost(). ¡Vamos con el código!
// handler del modulo static int myapmodHandler( request_rec *reqrec) // request data { // test handler if (! reqrec->handler || strcmp(reqrec->handler, "myapmod")) return DECLINED; // set del apropiado content type ap_set_content_type(reqrec, "text/html"); // test método http if (reqrec->method_number == M_GET) { // petición GET: escribe solo "Hello, World!" ap_rprintf(reqrec, "GET request: Hello, world!"); } else if (reqrec->method_number == M_POST) { // petición POST: lee POST data char *data; if (readPost(reqrec, &data) != -1) { // escribe "Hello, World!" y enseña POST data ap_rprintf(reqrec, "POST request: Hello, world! postdata: %s", data); } else { // POST data no disponible: escribe solo "Hello, World!" ap_rprintf(reqrec, "POST request: Hello, world!"); } } else return HTTP_METHOD_NOT_ALLOWED; // sale con OK return OK; } // función para leer POST data static int readPost( request_rec *reqrec, // request data char **data) // buffer de destinazion paraer POST data { // setup del client para permitir que Apache lea el request body if (ap_setup_client_block(reqrec, REQUEST_CHUNKED_ERROR) == OK) { // determina si el client ha enviado datos if (ap_should_client_block(reqrec)) { // lee los datos de POST char argsbuffer[HUGE_STRING_LEN]; int rsize, len_read, rpos=0; long length = reqrec->remaining; *data = (char*)apr_pcalloc(reqrec->pool, length +1); // loop de inserción datos en el buffer de destinación while ((len_read = ap_get_client_block(reqrec, argsbuffer, sizeof(argsbuffer))) > 0) { if ((rpos + len_read) > length) rsize = length - rpos; else rsize = len_read; // copia un bloque de datos memcpy((char *)*data + rpos, argsbuffer, rsize); rpos += rsize; } // POST data leido: return OK return 0; } } // sale con NOK return -1; }Ok, como se nota es ampliamente comentado y así se auto-explica, por lo cual no voy a detenerme sobre las instrucciónes y/o grupos de instrucciones (¡leer los comentarios! ¡Están ahí para eso!), pero voy a añadir, solamente, algunos detalles estructurales.
la función original myapmodHandler() se ha convertido en una especie de función helloworld (bueno, en práctica lo era incluso antes), que distingue los tipos de petición y, en el caso POST, llama a la función readPost() y escribe como respuesta "Hello, world!" mas los datos. En el caso de peticiones GET o POST sin datos se limita a escribir "Hola, mundo!", y para otros tipos de peticiones sale con error.
La función readPost() es simple, pero no es inmediata: en su tiempo la derivé de el único ejemplo interesante y bien hecho que encontré, y que estaba en el (óptimo) libro "Writing Apache Modules with Perl and C", que os recomiendo. Yo diría que en la última versión de Apache, la 2.4, se han introducido (y descritos aquí) otros métodos de extracción de datos de POST (¡os deseo buena lectura!), de todas maneras yo me la arreglé así, y el módulo funcionaba (y funciona todavía) muy bien.
¡Ah, el test, se me olvidaba! Testear con peticiones POST no es simple cómo hacerlo con las GET, adonde es suficiente (como dicho en el post anterior) escribir la URI del modulo en la barra de direcciones de Firefox o Chrome y esperar la respuesta. Para probar el nuestro nuevo módulo es mejor usar un buen plugin (como Poster, por ejemplo) que nos facilitará mucho la tarea.
Vale, de momento, creo que podemos parar por un tiempo el tema módulos de Apache/lighttpd. Juro que en el próximo post voy a hablar de otra cosa, no me gustaría que pensaran que el C se utiliza sólo para Web Servers...
¡Hasta el próximo post!
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.
Sí, 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...
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!):
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:
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...)
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:
¡Hasta el próximo post!
Bunny: A él no le importa nada. Es un nihilista.
El Nota: Eso debe ser agotador.
Sí, 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... |
// 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/helloworldtendremo 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!
Suscribirse a:
Comentarios (Atom)


