From the documentation of RAVAL, the MIT library of the books: the same as in its repository.
This page is available in Italian only for now.
RServer — API JSON su Kestrel
Sorgenti: RAVAL\HTTP\ (RServer.cs, RRequest.cs) · namespace RAVAL. Spiegazione completa con esempi: TUTORIAL.md, capitolo 14; thread e await nel capitolo 2.
Un programma (con o senza finestre) risponde a richieste HTTP con dati JSON. Ogni percorso è collegato a un metodo con un nome che riceve la RRequest e restituisce l'oggetto da mandare; niente lambda, niente controller, niente attributi.
var server = new RServer(5080)
.Get("/api/macchine", Elenco) // object? Elenco(RRequest r)
.Get("/api/macchine/{id}", Macchina)
.Post("/api/ordini", NuovoOrdine)
.OnUiThread(); // i metodi possono toccare i controlli
server.Start(); // torna quando ascolta
...
server.Stop();
RServer
| Membro | Fa |
|---|---|
new RServer(porta = 0) | il server; 0 = una porta libera scelta dal sistema |
Get, Post, Put, Delete(percorso, metodo) | un percorso con il suo metodo object? M(RRequest r); parametri tra graffe: /api/x/{id} |
GetAsync, PostAsync(percorso, metodo) | lo stesso con un metodo async Task<object?> M(RRequest r) |
OnUiThread() | i metodi girano sul thread dell'interfaccia (serve un'app Avalonia avviata) |
OnAllInterfaces() | ascolta su tutte le schede di rete, non solo su 127.0.0.1 |
Json | le JsonSerializerOptions (camelCase, come JsonSerializerDefaults.Web); si cambiano prima dell'avvio |
Start() · Stop() | avvio e arresto senza await: tornano quando il server ascolta / è fermo |
Run() | avvia e resta fermo finché un altro thread chiama Stop: new Thread(server.Run).Start() |
WaitStarted(limite) | aspetta che il server avviato con Run sia in ascolto |
StartAsync() · StopAsync() | avvio e arresto con await |
Dispose() · DisposeAsync() | come Stop, per using e await using |
Avviato, Addresses, Porta | lo stato; Addresses ha la porta vera («http://127.0.0.1:51234») |
Percorsi e opzioni si danno prima dell'avvio: dopo, InvalidOperationException.
RRequest
| Membro | Dà |
|---|---|
Param(nome) | parametro del percorso, poi della query (?nome=); null se manca |
Int(nome) | lo stesso come int; se non è un numero lancia RHttpError(400) |
Body<T>() | il corpo JSON come oggetto; se manca o è sbagliato lancia RHttpError(400) |
Text | il corpo come testo, già letto |
Header(nome) | un'intestazione; null se manca |
Metodo, Percorso | GET/POST..., il percorso chiesto |
Contesto | l'HttpContext di ASP.NET Core, per tutto il resto |
La risposta
| Il metodo... | Risposta |
|---|---|
| restituisce un oggetto | 200, JSON |
restituisce null | 204, senza corpo |
restituisce RResponse (Created(x), NotFound(msg), Error(codice, msg), new RResponse(codice, x)) | quel codice, con il suo corpo |
lancia RHttpError(codice, messaggio) | quel codice con { "error": "messaggio" } (RErrorMessage) |
| lancia un'altra eccezione | 500 con { "error": messaggio }; il server continua |
| percorso che non esiste | 404 di Kestrel, senza corpo |
Perché è fatta così
- Kestrel senza il resto di ASP.NET.
WebApplication.CreateSlimBuilder, il log di ASP.NET spento, unMapMethodsper percorso con unRequestDelegateche è un metodo della classe privataRotta. Il programma vede soloRRequeste oggetti, nonHttpContexte middleware. - Metodi con un nome. Come in tutto RAVAL: il metodo riceve la richiesta già letta (corpo compreso) e restituisce un oggetto; la chiamata sul thread dell'interfaccia passa per la classe
Chiamata, così il Dispatcher riceve un metodo e non una lambda. - Errori che diventano risposte. Un parametro sbagliato o un corpo illeggibile sono un 400 senza che il metodo debba controllarli; un'eccezione qualsiasi è un 500 e non ferma il server.
- Tre modi di avviarlo.
Start/Stopper chi non usa ancoraawait,Runper chi ragiona con un thread dedicato (fermo su unManualResetEventSlimcheStopAsyncaccende),StartAsync/StopAsyncper chi usaawait. - Solo su 127.0.0.1 per default. Farsi raggiungere dalla rete è una scelta esplicita (
OnAllInterfaces), perché da quel momento chiunque nella rete può chiamare l'API. FrameworkReference Microsoft.AspNetCore.App, non un pacchetto: Kestrel arriva dal framework condiviso dell'SDK. Chi usa RAVAL comeProjectReferencelo eredita nel suoruntimeconfig.json; chi usa la DLL conHintPath(come Albatros) no, e finché non usaRServerl'assembly ASP.NET non viene mai caricato.
Inciampi
StopAsync().GetAwaiter().GetResult()dal thread dell'interfaccia si blocca per sempre. DentroStopAsyncdi ASP.NET unawaittorna sul contesto di chi chiama; se chi chiama è il thread dell'interfaccia fermo ad aspettare, nessuno va avanti. Per questoStarteStopsonoTask.Run(AvviaAsync)eTask.Run(FermaAsync)piùGetAwaiter().GetResult(): il lavoro gira sul pool, dove non c'è un contesto da riprendere.TEST_RAVALlo prova chiamandoStopdal thread dell'interfaccia.ListenLocalhostnon accetta la porta 0. Per «una porta libera su 127.0.0.1» si usaListen(IPAddress.Loopback, 0); la porta vera si legge poi daapp.Urls(Addresses).- Con
OnUiThreaduna richiesta fatta dal thread dell'interfaccia, aspettata conResult, si blocca: il server aspetta il thread, il thread aspetta la risposta. Nelle prove si fa partire la richiesta e si fa girare il Dispatcher finché non arriva (classeAttesaCompitoinTEST_RAVAL\ProveHttp.cs). StopAsyncdà alle richieste in corso al più 5 secondi, poi chiude comunque: una richiesta ferma ad aspettare il thread dell'interfaccia non tiene il programma aperto per sempre.