Inviare richieste batch con ASP.NET WebAPI 2

di Moreno Gentili, in ASP.NET Web API,

ASP.NET Web API si rivela un'ottima tecnologia per esporre un servizio ai nostri utenti remoti. Tuttavia, le latenze di rete e l'inevitabile overhead del protocollo HTTP potrebbero limitare la loro capacità di inviare numeri elevati di richieste nell'unità di tempo.

Il supporto al batching di ASP.NET Web API 2 è un efficace rimedio a questo problema, perché consente l'invio di molteplici richieste HTTP con un singolo round-trip al server.
Il guadagno prestazionale non è facilmente stimabile a causa di vari fattori ma, in linea generale, a trarne maggior beneficio saranno quei client che inviano al servizio molte piccole richieste in rapida sequenza.

Di per sé, il batching non implica che le richieste verranno eseguite nell'ambito di una transazione. Piuttosto, il suo scopo è quello di farci raggiungere un throughput più elevato, opzionalmente mantenendo un'esecuzione sequenziale delle richieste.

Lato server, andiamo a configurare un'apposita route da destinare a questo scopo. Aggiungiamo le seguenti linee di codice al corpo del metodo Register, che si trova nel file App_Start/WebApiConfig.cs di un tipico progetto ASP.NET Web API.

config.Routes.MapHttpBatchRoute(
  routeName: "batch",
  routeTemplate: "api/batch",
  batchHandler: new DefaultHttpBatchHandler(GlobalConfiguration.DefaultServer){
      ExecutionOrder = BatchExecutionOrder.Sequential 
      //indichiamo NonSequential se l'ordine sequenziale non ci interessa
  }
);

Il DefaultHttpBatchHandler si occuperà di estrarre dal batch tutte le richieste HTTP e di reintrodurle, una ad una, nella pipeline di elaborazione del servizio, proprio come se fossero arrivate singolarmente. Successivamente, aggiungerà i loro risultati al contenuto di un'unica risposta.

Lato client, prepariamo la richiesta batch usando le classi del namespace System.Net.Http.

//Definiamo l'URL base del servizio ASP.NET Web API.
string baseAddress = "http://localhost:56813/";

//Iniziamo col preparare le singole richieste HTTP da includere nel batch

//In questo esempio, le prime due sono richieste POST per inviare dei dati...
HttpRequestMessage inserisciTemperaturaRoma = new HttpRequestMessage(
  HttpMethod.Post, baseAddress + "api/temperature/roma") { Content = 
    new ObjectContent<string>("15.5 °C", new JsonMediaTypeFormatter())
};
HttpRequestMessage inserisciTemperaturaMilano = new HttpRequestMessage(
  HttpMethod.Post, baseAddress + "api/temperature/milano") { Content = 
    new ObjectContent<string>("12.7 °C", new JsonMediaTypeFormatter())
};
//...e la terza è una richiesta GET per ottenere un valore
HttpRequestMessage leggiMediaItaliana = new HttpRequestMessage(
  HttpMethod.Get, baseAddress + "api/temperature/italia/media");

//Creiamo la richiesta batch vera e propria, assegnandole un contenuto 
//multipart/mixed. Il MultipartContent ci consente di aggiungere le 3 
//richieste che abbiamo appena preparato
HttpRequestMessage richiestaBatch = new HttpRequestMessage(
  HttpMethod.Post, baseAddress + "api/batch")
  {
    Content = new MultipartContent("mixed"){
          new HttpMessageContent(inserisciTemperaturaRoma),
          new HttpMessageContent(inserisciTemperaturaMilano),
          new HttpMessageContent(leggiMediaItaliana)
    }
};

//Finalmente inviamo la richiesta batch usando un'istanza di HttpClient
HttpClient client = new HttpClient();
HttpResponseMessage rispostaBatch = client.SendAsync(richiestaBatch).Result;

//Assicuriamoci che la richiesta batch abbia avuto successo
rispostaBatch.EnsureSuccessStatusCode();

//Anche la risposta sarà di tipo multipart/mixed, ovvero conterrà 
//un insieme di contenuti
IEnumerable<HttpContent> contenuti = rispostaBatch.Content
  .ReadAsMultipartAsync().Result.Contents;

//Ognuno dei contenuti è l'HttpResponseMessage relativo ad una nostra 
//richiesta. In questo esempio, ci interessa esaminare solo l'ultima risposta, 
// quella relativa alla richiesta GET
HttpResponseMessage rispostaTemperaturaMedia = contenuti.Last()
  .ReadAsHttpResponseMessageAsync().Result;

//Il contenuto della risposta sarà una stringa JSON...
string contenutoJson = rispostaTemperaturaMedia
  .Content.ReadAsStringAsync().Result;

//...che deserializziamo in questo modo
string temperaturaMedia = JsonConvert
  .DeserializeObject<string>(contenutoJson);

//Il valore ottenuto possiamo usarlo in un messaggio da mostrare a video
string messaggio = string.Format(
  "La media italiana è di {0}", temperaturaMedia);

Come si vede dall'esempio, siamo liberi di inserire nel batch varie richieste HTTP, siano esse GET, POST o di altro tipo.

L'interoperabilità è uno dei punti di forza di ASP.NET Web API, infatti l'utente non sarà in alcun modo costretto ad usare un client .NET ma potrà preparare la richiesta batch usando un linguaggio a sua scelta. L'importante è che il suo contenuto sia conforme alla specifica multipart/mixed (http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html), così come descritta dal W3C.

Ed ecco il contenuto grezzo della nostra richiesta batch: se lo esaminiamo, noteremo che ogni richiesta incapsulata al suo interno è stata ben isolata grazie ad un boundary che agisce da delimitatore.

POST http://localhost:56813/api/batch HTTP/1.1
Content-Type: multipart/mixed; boundary="bff10b16-941b-4128-b8c8-0e6589c475ed"
Host: localhost:56813
Content-Length: 654
Expect: 100-continue
Connection: Keep-Alive

--bff10b16-941b-4128-b8c8-0e6589c475ed
Content-Type: application/http; msgtype=request

POST /api/temperature/roma HTTP/1.1
Host: localhost:56813
Content-Type: application/json; charset=utf-8

"15.5 °C"
--bff10b16-941b-4128-b8c8-0e6589c475ed
Content-Type: application/http; msgtype=request

POST /api/temperature/milano HTTP/1.1
Host: localhost:56813
Content-Type: application/json; charset=utf-8

"12.7 °C"
--bff10b16-941b-4128-b8c8-0e6589c475ed
Content-Type: application/http; msgtype=request

GET /api/temperature/italia/media HTTP/1.1
Host: localhost:56813


--bff10b16-941b-4128-b8c8-0e6589c475ed--

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi