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.

sabato 5 novembre 2016

Il grande lighttpd
come scrivere un modulo lighttpd in C - pt.3

    Drugo: E se poi quello se la prende?   
    Bunny: A lui non importa niente di niente, è un nichilista.
    Drugo: Ah, dev’essere faticoso da morire.

Ebbene si, siamo faticosamente arrivati al capitolo finale, oggi si scrive il codice del nostro mini-modulo lighttpd. Uff, che fatica per il Drugo...
...che fatica...
Ma prima vediamo un attimo come è strutturato un modulo. Eccovi un piccolo schema esemplificativo (estratto dal nostro mod_helloworld.c creato nel post precedente: rileggere subito il post precedente!):
// 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;
}
Come si nota si usano delle macro (gentilmente fornite dall'ambiente di sviluppo lighttpd) per creare delle callback con cui inizializzare una struttura plugin che poi lighttpd userà per gestire il nostro modulo ogni qualvolta arrivi una petizione HTTP che ne richieda l'uso. Per chi vuole approfondire l'argomento callback è già stato ampiamente descritto qui e qui (immagino abbiate già letto quei post... o no?).

Allora, che parti del nostro modulo dobbiamo modificare per fare in modo che, usandolo, ci scriva "Hello, world!" nel browser? Visto che il problema è abbastanza semplice anche il codice da scrivere non sarà molto voluminoso e complesso: ci basterà modificare solo la callback mod_helloworld_uri_handler nella seguente maniera:
URIHANDLER_FUNC(mod_helloworld_uri_handler)
{
    plugin_data *p = p_d;
    UNUSED(srv);

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

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

    mod_helloworld_patch_connection(srv, con, p);

    // test handler (return se errore)
    if (con->uri.path->ptr && strstr(con->uri.path->ptr, "helloworld")) {
        // scrive 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 finito
        return HANDLER_FINISHED;
    }
    else
        return HANDLER_GO_ON;
}
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.

Alcune parti sono rimaste identiche alla callback originale (ma ho aggiunto dei commenti), e sono tipiche della struttura del codice di lighttpd: per non complicare troppo l'argomento non mi dilungherò su queste parti. Comunque fidatevi perché lighttpd è scritto e funziona come si deve.

La parte veramente nuova che ho scritto è quella che inizia col commento "test handler (return se errore)", dove si testa se la URI passata al browser contiene il nome del modulo e, in quel caso, esegue quello che vogliamo: copia in un buffer la scritta "Hello, world!" e la invia come risposta alla petizione. Quindi se noi scriviamo nella barra degli indirizzi di Firefox, Chrome, Opera (o qualsiasi altro browser meno IE, per favore...)
http://127.0.0.1/helloworld
avremo come risposta nel browser:
Hello, world!
Ho scritto 127.0.0.1 (e si poteva scrivere anche localhost) perché, ovviamente, il primo test lo faremo in locale. Se poi avete voglia di aprire la vostra macchina al mondo esterno e usarla come un vero Web Server (in una rete locale o nel WWW) potrete ripetere il test usando il vostro indirizzo IP pubblico e otterrete lo stesso risultato. Il modulo funziona!

Il codice appena mostrato l'ho scritto e provato con lighttpd 1.4.33. Se usate una versione più recente (dalla 1.4.36 in avanti) scoprirete che qualcosa è cambiato a livello di gestione dei buffer, quindi ad esempio le linee:   
if (con->uri.path->used == 0)
...
buffer *buf = chunkqueue_get_append_buffer(con->write_queue);
devono diventare:   
if (buffer_is_empty(con->uri.path))
...
buffer *buf = buffer_init();
ma questi sono dettagli. In futuro, come promesso, vi proporrò qualche modulo più sofisticato, con modifiche anche alle altre callback del modulo, ma questo per il momento può bastare, non gettiamo troppa carne al fuoco, se no ci stanchiamo come il Drugo...

Ciao e al prossimo post!

Nessun commento:

Posta un commento