Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

giovedì 8 dicembre 2016

L'ultimo degli Apache III - Il ritorno
come scrivere un modulo Apache in C - pt.3

Allora, facciamo il punto: negli ultimi tre post abbiamo parlato di moduli lighttpd (qui, qui e ancora qui). Nel primo dei tre avevo ricordato che l'argomento non era nuovo: anticamente avevo scritto due post (qui e qui) in cui si parlava di moduli per Apache, che è un parente stretto di lighttpd (famiglia Web Servers). Ecco, per chiudere in bellezza vi propongo un terzo episodio de "L'ultimo degli Apache" (o dei Mohicani?), con una (spero) interessante ampliazione di quello che si era scritto.
...veramente sono un Mohicano, non un Apache...
Bene, ne "L'ultimo degli Apache II - La vendetta" (che, avete appena riletto, immagino...) avevamo scritto un bel modulo elementare per Apache. Faceva poco (scriveva solo una presentazione nel browser) però era un buon inizio per entrare nel mondo dei moduli per Web Servers. Ecco, adesso riprenderemo quel modulo e gli aggiungeremo una funzione che estrae i dati POST inclusi in una eventuale petizione HTTP di tipo POST che arriva al modulo. Perché ho scelto di aggiungere proprio questa funzionalità? Beh, ovviamente perchè è abbastanza normale che un modulo tratti vari tipi di petizioni (GET, POST, ecc.), e poi, in particolare, ricordo che la prima volta che aggiunsi questa funzionalità in un modulo Apache che avevo scritto, notai che non erano disponibili molte indicazioni su questo argomento (e questo nonostante la enorme mole di documentazione Apache disponibile rispetto a quella di lighttpd).

Bene, senza stare a ripetere tutto il codice e la maniera di generarlo (c'è già tutto in "L'ultimo degli Apache II - La vendetta") riscriveremo solo la funzione myapmodHandler() e aggiungeremo una nuova funzione getPost(). Vai col codice!
// handler del modulo
static int myapmodHandler(
    request_rec *reqrec)            // request data
{
    // test handler
    if (! reqrec->handler || strcmp(reqrec->handler, "myapmod"))
        return DECLINED;

    // set del appropriato content type
    ap_set_content_type(reqrec, "text/html");

    // test metodo http
    if (reqrec->method_number == M_GET) {
        // petizione GET: scrive solo "Hello, World!"
        ap_rprintf(reqrec, "GET request: Hello, world!");
    }
    else if (reqrec->method_number == M_POST) {
        // petizione POST: legge POST data
        char *data;
        if (readPost(reqrec, &data) != -1) {
            // scrive "Hello, World!" e mostra POST data
            ap_rprintf(reqrec, "POST request: Hello, world! postdata: %s", data);
        }
        else {
            // POST data non disponibile: scrive solo "Hello, World!"
            ap_rprintf(reqrec, "POST request: Hello, world!");
        }
    }
    else
        return HTTP_METHOD_NOT_ALLOWED;

    // esce con OK
    return OK;
}

// funzione per leggere POST data
static int readPost(
    request_rec *reqrec,            // request data
    char        **data)             // buffer di destinazione per POST data
{
    // setup del client per permettera che Apache legga il request body
    if (ap_setup_client_block(reqrec, REQUEST_CHUNKED_ERROR) == OK) {
        // determina se il client ha spedito dati
        if (ap_should_client_block(reqrec)) {
            // legge i dati di 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 di inserzione dati nel buffer di destinazione
            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 blocco di dati
                memcpy((char *)*data + rpos, argsbuffer, rsize);
                rpos += rsize;
            }

            // POST data letto: return OK
            return 0;
        }
    }

    // esce con NOK
    return -1;
}
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale.

L'originale funzione myapmodHandler() è diventata una sorta di funzione helloworld (beh, in pratica lo era anche prima), che distingue i tipi di petizione e, nel caso POST, chiama la funzione readPost() e scrive come risposta "Hello, world!" più i dati. Nel caso di petizioni GET o di petizioni POST senza dati si limita a scrivere "Hello, world!", e per altri tipi di petizione esce con errore.

La funzione readPost() è semplice ma non immediata: a suo tempo la derivai dall'unico esempio interessante e ben fatto che trovai, e che stava nel (ottimo) libro "Writing Apache Modules with Perl and C", che vi raccomando. Direi che nell'ultima versione di Apache, la 2.4, sono stati introdotti (e descritti qui) altri metodi di estrazione dei dati POST (vi auguro una buona lettura!), comunque io me l'ero cavata così, e il modulo funzionava (e funziona tutt'ora) benissimo.

Ah, il test, dimenticavo! Testare con petizioni POST non è semplice come farlo con le GET, dove è sufficiente (come detto nel post precedente) scrivere la URI del modulo nella barra degli indirizzi di Firefox o Chrome e aspettare la risposta. Per testare il nostro nuovo modulo è meglio usare un bel plugin del browser (tipo Poster) per semplificare il lavoro.

Va bene, per il momento credo che possiamo fermare per un tempo l'argomento moduli Apache/lighttpd. Giuro che nel prossimo post parlerò d'altro, non vorrei che pensaste che il C si usa solo per i Web Servers...

Ciao e al prossimo post!

Nessun commento:

Posta un commento