Analizzare le limitazioni di un'app Web basata su polling
L'architettura corrente dell'applicazione segnala le informazioni sulle scorte recuperando tutte le informazioni sui titoli azionari dal server in base a un timer. Questa progettazione è spesso definita progettazione basata su polling.
Server
Le informazioni sui prezzi dei titoli azionari vengono archiviate in un database Azure Cosmos DB. Quando viene attivata da una richiesta HTTP, la funzione getStocks
restituisce tutte le righe dal database.
import { app, input } from "@azure/functions";
const cosmosInput = input.cosmosDB({
databaseName: 'stocksdb',
containerName: 'stocks',
connection: 'COSMOSDB_CONNECTION_STRING',
sqlQuery: 'SELECT * from c',
});
app.http('getStocks', {
methods: ['GET'],
authLevel: 'anonymous',
extraInputs: [cosmosInput],
handler: (request, context) => {
const stocks = context.extraInputs.get(cosmosInput);
return {
jsonBody: stocks,
};
},
});
- Recuperare i dati: La prima sezione del codice, cosmosInput, ottiene tutti gli elementi nella tabella
stocks
, con la querySELECT * from c
, nel databasestocksdb
in Cosmos DB. - Restituire dati: La seconda sezione di codice, app.http, riceve i dati nella funzione come input in
context.extraInputs
e quindi lo restituisce come corpo della risposta al client.
Client
Il client di esempio usa Vue.js per comporre l'interfaccia utente e il client di recupero per gestire le richieste all'API.
La pagina HTML usa un timer per inviare una richiesta al server ogni cinque secondi per richiedere i titoli azionari. La risposta restituisce una matrice di azioni, che vengono quindi visualizzate all'utente.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4=" crossorigin="anonymous" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Stocks | Enable automatic updates in a web application using Azure Functions and SignalR</title>
</head>
<body>
<!-- BEGIN: Replace markup in this section -->
<div id="app" class="container">
<h1 class="title">Stocks</h1>
<div id="stocks">
<div v-for="stock in stocks" class="stock">
<div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
<div class="change">Change:
<span :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
{{ stock.changeDirection }}{{ stock.change }}
</span></div>
</div>
</div>
</div>
<!-- END -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
<script src="bundle.js" type="text/javascript"></script>
</body>
</html>
import './style.css';
function getApiUrl() {
const backend = process.env.BACKEND_URL;
const url = (backend) ? `${backend}` : ``;
return url;
}
const app = new Vue({
el: '#app',
interval: null,
data() {
return {
stocks: []
}
},
methods: {
async update() {
try {
const url = `${getApiUrl()}/api/getStocks`;
console.log('Fetching stocks from ', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
app.stocks = await response.json();
} catch (ex) {
console.error(ex);
}
},
startPoll() {
this.interval = setInterval(this.update, 5000);
}
},
created() {
this.update();
this.startPoll();
}
});
Quando il metodo startPoll
inizia il polling, il metodo update
viene chiamato ogni cinque secondi. All'interno del metodo update
, viene inviata una richiesta GET all’endpoint API /api/getStocks
e il risultato viene impostato su app.stocks
, che aggiorna l'interfaccia utente.
Il codice server e client è relativamente semplice: ottenere tutti i dati, visualizzare tutti i dati. Come scopriamo nell'analisi, questa semplicità porta con sé alcune limitazioni.
Analisi della soluzione prototipo
In qualità di tecnico di Tailwind Traders, sono stati identificati alcuni degli svantaggi di questo approccio di polling basato su timer.
Richieste API non necessarie: Nel prototipo di polling basato su timer l'applicazione client contatta il server indipendentemente dal fatto che siano presenti modifiche ai dati sottostanti.
Aggiornamenti di pagina non necessari: Una volta restituiti i dati dal server, l'intero elenco di scorte viene aggiornato nella pagina Web, anche se non sono stati modificati dati. Questo meccanismo di polling è una soluzione poco efficiente.
intervalli di polling: Anche selezionare l'intervallo di polling migliore per lo scenario in uso è complicato. Il polling costringe a effettuare una scelta tra il costo di ogni chiamata al back-end e la rapidità con cui si vuole che l'app risponda ai nuovi dati. I ritardi spesso si verificano tra il tempo in cui i nuovi dati diventano disponibili e il tempo in cui l'app lo rileva. La figura seguente illustra il problema.
Nel peggiore dei casi il ritardo potenziale del rilevamento di nuovi dati corrisponde all'intervallo di polling. Quindi perché non usare un intervallo più breve?
Quantità di dati: Di pari passo con il ridimensionamento dell'applicazione, la quantità di dati scambiati tra client e server diventa un problema. Ogni intestazione di una richiesta HTTP include centinaia di byte di dati insieme al cookie della sessione. Tutto questo sovraccarico, specialmente con carichi importanti, comporta uno spreco di risorse e grava inutilmente sul server.
Ora che si ha familiarità con il prototipo, è possibile eseguire l'applicazione nel computer.
Supporto di CORS
Nel file local.settings.json dell'app per le funzioni, la sezione Host
include le impostazioni seguenti.
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "<STORAGE_CONNECTION_STRING>",
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"COSMOSDB_CONNECTION_STRING": "<COSMOSDB_CONNECTION_STRING>"
},
"Host" : {
"LocalHttpPort": 7071,
"CORS": "http://localhost:3000",
"CORSCredentials": true
}
}
Questa configurazione consente a un'applicazione Web in esecuzione all'indirizzo localhost:3000 di effettuare richieste all'app per le funzioni in esecuzione all'indirizzo localhost:7071. La proprietà CORSCredentials
indica all'app per le funzioni di accettare i cookie delle credenziali dalla richiesta.
CORS (Cross-Origin Resource Sharing) è una funzionalità HTTP che consente a un'applicazione Web in esecuzione in un dominio di accedere a risorse in un altro dominio. Nei browser Web è implementata una restrizione di sicurezza nota come criterio della stessa origine che impedisce a una pagina Web di chiamare API in un dominio differente. CORS offre una modalità sicura per consentire a un dominio (quello di origine) di chiamare API in un altro dominio.
Durante l'esecuzione in locale, CORS viene configurato automaticamente nel file di local.settings.json dell'esempio, che non viene mai pubblicato. Quando si distribuisce l'app client (unità 7), è necessario aggiornare anche le impostazioni CORS nell'app per le funzioni per consentire l'accesso dall'app client.