In questo articolo, esploreremo passo dopo passo come sviluppare un server HTTP in Dart. Partiremo dalle basi, illustrando come creare un server di base in grado di gestire le richieste in arrivo. Successivamente, approfondiremo argomenti più avanzati come la gestione delle rotte, l'elaborazione di richieste e risposte, e l'implementazione di middleware.
Inizializziamo il progetto
Per la creazione del nuovo progetto Dart, dal terminale di VSCode digitiamo i seguenti comandi
dart create dart_server_http
cd dart_server_http
code .
Aperto il progetto, rimuoviamo le cartelle lib e test (per il nostro scopo non ci occorrono) e modifichiamo il file bin/dart_server_http in questo modo
void main(List<String> arguments) {
}
La versione base
Per gestire le rotte (inizialmente solo quella principale "/"), aggiungiamo le dipendenze necessarie, lavorando sempre dal terminale:
flutter pub add shelf
flutter pub add shelf_router
aggiungiamo quindi il Router(), definiamo la rotta "/" e l'handler che sarà invocato
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_router/shelf_router.dart';
final router = Router()
..get('/', _rootHandler)
;
Response _rootHandler(Request req) {
return Response.ok('Hello, World!\n');
}
Modifichiamo il metodo main() in questo modo per avviare l'ascolto del server (chiaramente sarà un metodo asincrono):
void main(List<String> arguments) async {
final ip = "127.0.0.1";
final port = 8080;
final handler = Pipeline().addHandler(router.call);
final server = await serve(handler, ip, port);
print('Server listening ${server.address.host}:${server.port}');
}
Avviamo il progetto e il nostro server sarà pronto a rispondere all'indirizzo 127.0.0.1 e sulla porta 8080 con il classico Hello, World! .
Aggiungiamo elementi avanzati
Nell'esempio precedente, abbiamo forzatamente indicato l'IP su cui lasciare in ascolto il server, ma possiamo anche aggiungere la dipendenza
import 'dart:io';
ed accettare connessioni da qualsiasi indirizzo, con questa notazione
final ip = InternetAddress.anyIPv4;
Allo stesso modo, possiamo eventualmente ricavare la porta dall'ambiente su cui sta girando il nostro server, con questa assegnazione
final port = int.parse(Platform.environment['PORT'] ?? '8080');
Possiamo ancora utilizzare un middleware per avere un log di tutte le richieste ricevute dal server, modificando il nostro handler:
e avere un log come questo:
final handler = Pipeline().addMiddleware(logRequests()).addHandler(router.call);
e avere un log come questo:
Server listening 0.0.0.0:8080
2025-01-31T14:26:11.287931 0:00:00.005945 GET [404] /flutter_service_worker.js
2025-01-31T14:26:14.993291 0:00:00.003056 GET [200] /
2025-01-31T14:26:17.044027 0:00:00.000067 GET [404] /flutter_service_worker.js
2025-01-31T14:26:24.823039 0:00:00.000336 GET [200] /
2025-01-31T14:26:26.853715 0:00:00.000052 GET [404] /flutter_service_worker.js
Da ultimo possiamo aggiungere una rotta residuale per tutte le URL non gestite, modificando il nostro router:
final router = Router()
..get('/', _rootHandler)
..all('/<ignored|.*>', (Request request) {
return Response.notFound('Page not found');
});
e quindi avere una risposta di questo tipo sulle rotte non definite:
E infine possiamo indicare che la risposta del server non sia semplice testo, ma contenuto HTML, aggiungendo il corretto Content-Type:
Response _rootHandler(Request req) {
return Response.ok('<h1>Hello, World!</h1>\n',
headers: {'Content-Type': 'text/html'});
}
Lavoriamo su rotte API
Potrebbe essere interessanti gestire delle rotte API che ritornino dati in formato JSON. Aggiungiamo una nuova dipendenzaimport 'dart:convert';
quindi definiamo una nuova classe che espone un nuovo router specifico:
class Api {
List<int> items = [1, 2, 3, 4, 5];
Future<Response> _items(Request request) async {
return Response.ok(jsonEncode(items),
headers: {'Content-Type': 'application/json'});
}
Router get router {
final router = Router();
router.get('/items', _items);
router.all('/<ignored|.*>', (Request request) => Response.notFound('null'));
return router;
}
}
Per semplicità i nostri dati sono rappresentati da un semplice array di numeri interi che viene opportunamente codificato in formato JSON.
Montiamo questa classe nel router principale:
final router = Router()
..get('/', _rootHandler)
..mount('/api/v1.0/', Api().router.call)
..all('/<ignored|.*>', (Request request) {
return Response.notFound('Page not found');
});
e quindi testiamo l'URL
Se volessimo gestire anche una chiamata POST, sarebbe sufficiente gestire la nuova rotta ad esempio in questo modo:
...
router.post('/items', (Request request) async {
final body = await request.readAsString();
final item = int.parse(body);
items.add(item);
return Response.ok(jsonEncode(items),
headers: {'Content-Type': 'application/json'});
});
...
Commenti
Posta un commento