Dart: realizzare un server HTTP

Di - 22 December 2012 - in

Eccoci ad un altro appuntamento con la rubrica dedicata a Dart, il nuovo e promettente linguaggio di casa Google orientato al web; nell’articolo di oggi ci occuperemo di un argomento mai affrontato finora, vedremo infatti come realizzare un server HTTP minimale.

Prima di passare all’argomento principale però è doveroso citare il rilascio del Milestone 2, traguardo molto importante sotto diversi aspetti, primo fra tutti l’ottimizzazione del codice Javascript generato. Rispetto al Milestone 1, dart2js può ora restituire output del 50% più leggero grazie alla tecnica del three-shaking applicata alle librerie html e ad una generale ottimizzazione.

Passiamo ora allo sviluppo del nostro piccolo server HTTP che, oltre a servire le pagine richieste all’indirizzo http://127.0.0.1:8080, terrà un log delle attività; le classi principali utilizzate in questo progetto sono: HttpServer(), File() e Directory(), definite nella libreria dart:io; il server è strutturato in modo tale da servire le pagine contenute nella directory da cui esso viene avviato, quindi lo si potrà testare localmente con una qualsiasi nostra web application o sito internet.

Nota: è bene che la directory di Dart sia inclusa nella variabile d’ambiente PATH così il server potrà essere lanciato col comando

dart /percorso/completo/di/server.dart

Come al solito iniziamo mostrando il listato completo del server, seguito dall’analisi degli elementi più importanti:

import 'dart:io';

err404(HttpResponse response) {
  response.statusCode = HttpStatus.NOT_FOUND;
  response.outputStream.writeString("Pagina non trovata");
  response.outputStream.close();
}

avviaServer(String basePath) {
  var server = new HttpServer();
  String host = '127.0.0.1';
  int port = 8080;
  server.listen(host,port);

  log("Server in ascolto all'indirizzo http://${host}:${port}");
  server.defaultRequestHandler = (HttpRequest request, HttpResponse response) {
    log('${new Date.now()} ${request.method} ${request.uri}');
    final String path = request.path == '/' ? '/index.html' : request.path;
    final File file = new File('${basePath}${path}');
    file.exists().then((bool trovato) {
      if (trovato) {
        file.fullPath().then((String fullPath) {
          if (!fullPath.startsWith(basePath)) {
            err404(response);
          } else {
            file.openInputStream().pipe(response.outputStream);
          }
        });
      } else {
        err404(response);
      }
    });
  };
}

log(msg){
  var script = new File(new Options().script);
  File logFile = new File('${script.directorySync().path}/log.txt');
  OutputStream output;
  output = logFile.openOutputStream(FileMode.APPEND);
  output.writeString('$msg \n');
}

main() {
  Directory dir = new Directory.current();
  avviaServer(dir.path);
}

Partiamo dall’entry point, la funzione main(): la prima istruzione istanzia un oggetto Directory(), il metodo current() si occuperà di recuperare le informazioni relative alla directory corrente, quella dalla quale il programma viene avviato; la seconda istruzione passa alla funzione avviaServer un oggetto di tipo String con il percorso della directory corrente.

Passiamo ora alla funzione principale del programma, avviaServer(), che inizializzerà un nuovo oggetto HttpServer, definirà host e port, metterà il server in ascolto e imposterà un request handler:

  var server = new HttpServer();
  String host = '127.0.0.1';
  int port = 8080;
  server.listen(host,port);
  server.defaultRequestHandler = (HttpRequest request, HttpResponse response) {
    . . .
    };

Il corpo del metodo defaultRequestHandler contiene una struttura di controllo che non abbiamo mai visto nel corso di questa rubrica, ovvero l’operatore ternario:

expr1 ? expr2 : expr3

final String path = request.path == '/' ? '/index.html' : request.path;

da leggere così: se request.path è ‘/’ allora il valore di path sarà '/index.html', altrimenti il valore sarà request.path.

A questo punto verrà creato un oggetto File con il parametro ${basePath}${path} (percorso alla directory principale seguito dal nome del file richiesto) e il programma controllerà se il file esista o meno. Tralasciamo l’analisi del blocco di controllo e concentriamoci invece sulla riga

file.openInputStream().pipe(response.outputStream);

questa istruzione crea per il file un nuovo stream di input indipendente e lo indirizza, .pipe(), allo stream di output di response, identificatore di HttpResponse che ne mostrerà il contenuto nel browser.

Se il file non esiste, quindi se file.exists() ritorna false, viene passata l’istanza di HttpResponse alla funzione err404(), che imposterà lo stato su NOT_FOUND e scriverà nello stream di output la stringa “Pagina non trovata”; con questo termina il codice relativo al server HTTP.

Prima di chiudere, è bene dare uno sguardo alla funzione log(), chiamata dal server per scrivere su file le richieste ricevute dall’utente:

  var script = new File(new Options().script);
  File logFile = new File('${script.directorySync().path}/log.txt');
  OutputStream output;
  output = logFile.openOutputStream(FileMode.APPEND);
  output.writeString('$msg \n');

La prima istruzione crea una nuova istanza di File(). L’attributo script dell’oggetto Options restituisce la stringa con il percorso del file in esecuzione, nel nostro caso /percorso/completo/di/server.dart, ed è importante specificarlo altrimenti il file di log verrebbe scritto nella directory dalla quale è stato avviato il server.

Il suffisso Sync in script.directorySync().path assicura che l’istruzione venga eseguita in modo sincrono, rispetto al codice, questo per garantire che le istruzioni successive siano eseguite solo quando essa è terminata.

Nelle ultime istruzioni viene creato un oggetto OutputStream, viene aperto il file e viene accodato il contenuto di $msg.

Per testare il server, spostiamoci in una directory che contiene una web app, un sito internet o una semplice pagina html e avviamolo:

dart /percorso/completo/di/server.dart

apriamo un browser e raggiungiamo l’indirizzo http://127.0.0.1:8080, poi spostiamoci nella directory che contiene server.dart e vediamo che è stato creato un file, log.txt che ha un contenuto simile a questo:

Server in ascolto all'indirizzo http://127.0.0.1:8080
2012-12-21 14:28:16.948 GET /
2012-12-21 14:28:17.006 GET /index.css
2012-12-21 14:28:17.187 GET /index.js
2012-12-21 14:28:17.223 GET /favicon.ico

Continueremo in futuro ad occuparci di attività server-side, vedremo come creare socket e svilupperemo applicazioni client/server più complesse. Nel prossimo articolo affronteremo un tema molto importante, gli Isolates, ovvero il mezzo tramite il quale Dart gestisce processi paralleli.

Leave a Reply

Claudio d'Angelis Articolo scritto da

Programmatore e studente di Informatica, appassionato di musica, web e sistemi UNIX. Collabora con Googlab dall'Ottobre 2012.

Contatta l'autore

Previous post:

Next post: