Maude: ¿Y en que te dedicas en tu tiempo libre?Dando por echo que para escribir un buen Software no hay necesidad de imitar los hábitos del mítico El Nota de El gran Lebowski, volvemos al tema lighttpd, porque en el tercer post de la serie (aquí, aquí y también aquí) prometí expandir la discusión. ¡Bien, ha llegado el momento, las promesas se mantienen!
El Nota: Bueno, ya sabes: jugar a los bolos, conducir por ahí, un viaje ácido de vez en cuando...
...Bueno, ya sabes: jugar a los bolos, conducir por ahí, un módulo lighttpd de vez en cuando... |
Pues bien, sin molestarse en repetir todo el código y la forma de generarlo, sólo reescribiremos la función mod_helloworld_uri_handler() y añadiremos una nueva función getPost(). Añado que, para integrar el código que he escrito, es necesario seguir la guía de instalación indicada en "El gran lighttpd - pt.1" y repetir los 10 pasos indicados en "El gran lighttpd - pt.2", usando esta vez la versión 1.4.45 de lighttpd (ojo: usando una versión anterior, el código que estoy a punto de mostrar no funciona). Hecho? entonces ¡Vamos con el código
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) size_t s_len = buffer_string_length(con->uri.path); if (s_len == 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")) { // prepara el buffer para la respuesta buffer *buf = buffer_init(); // test método http if (strstr(con->request.request->ptr, "GET")) { // método GET: escribe respuesta buffer_append_string(buf, "<big>Hello, world!</big>"); } else if (strstr(con->request.request->ptr, "POST")) { // método POST: controla la presencia de post-data size_t len = con->request.content_length; if (len) { // post-data presentes; los lee para escribir la respuesta char *data = malloc(len + 1); if (readPost(con, data, len)) { // escribe la respuesta buffer_append_string(buf, data); } // libera la memoria free(data); } else { // error: mensaje POST sin post-data buffer_append_string(buf, "mod_helloworld: error: POST sin post-data"); } } else { // error: mensaje con método no tratado buffer_append_string(buf, "mod_helloworld error: método no tratado"); } // escribe el buffer y lo libera chunkqueue_append_buffer(con->write_queue, buf); buffer_free(buf); // envía el 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; } // handler no encontrado return HANDLER_GO_ON; } static bool readPost( connection *con, // datos conexión char *data, // buffer destinación para post-data size_t len) // longitud data (sin terminador) { // set valor de return de default int retval = false; // lee post-data (en el stream de chunks) size_t rpos = 0; chunkqueue *cq = con->read_queue; for (chunk *mychunk = cq->first; mychunk; mychunk = cq->first) { // calcula el size del buffer correspondiente al chunk de post-data corriente size_t n_tocopy = buffer_string_length(mychunk->mem) - mychunk->offset; // test si hay datos a copiar if (n_tocopy <= (len - rpos)) { // copia un chunk y set de la posición de copia del proximo chunk memcpy(data + rpos, mychunk->mem->ptr + mychunk->offset, n_tocopy); rpos += n_tocopy; // ha sido leído (almeno) un chunk de post-data: set retval=true retval = true; } else { // buffer overflow: salida forzada del loop break; } // indica como leído el chunk de post-data corriente chunkqueue_mark_written(cq, chunkqueue_length(cq)); } // añade el terminadore de string (el buffer es largo len+1) data[len] = 0; // sale con retval return retval; }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 mod_helloworld_uri_handler() sigue siendo una especie de función "Helloworld", que ahora 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 se limita a escribir "Hello, world!" y, para otros tipos de peticiones o con peticiones POST sin datos, sale con error.
La función readPost() es simple, pero no es inmediata: dada la casi total falta de ejemplos disponibles en la red, he tenido que hacer, prácticamente, reverse engineering sobre los (excelentes) módulos integrados en la distribución (¡tener el código fuente original disponible siempre es una gran cosa!) de manera de deducir cómo hacer lo que tenia como objetivo. El resultado final (modestia aparte) es bueno, tanto en apariencia (estilo) como en rendimiento (hice algunas pruebas y funciona 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 (o cualquier otro navegador menos IE, por favor...) 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 lighttpd. Juro que en el próximo post voy a hablar de otra cosa, no me gustaría que piensen que el C se utiliza sólo para Web Servers...
¡Hasta el próximo post!