De beperkingen van een web-app op basis van polling analyseren
De huidige architectuur van de toepassing rapporteert voorraadgegevens door alle aandelenkoersinformatie van de server op te halen op basis van een timer. Dit ontwerp wordt vaak een ontwerp op basis van polling genoemd.
Server
De informatie over de aandelenkoers wordt opgeslagen in een Azure Cosmos DB-database. Wanneer deze wordt geactiveerd door een HTTP-aanvraag, retourneert de functie getStocks
alle rijen uit de 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,
};
},
});
- Gegevens ophalen: De eerste sectie met code, cosmosInput, haalt alle items in de
stocks
tabel, met de querySELECT * from c
, op in destocksdb
database in Cosmos DB. - Retourgegevens: Het tweede codegedeelte, app.http, ontvangt die gegevens als invoer in
context.extraInputs
de functie en retourneert deze als antwoordtekst terug naar de client.
Klant
De voorbeeldclient maakt gebruik van Vue.js om de gebruikersinterface en de fetch-client samen te stellen voor het verwerken van aanvragen voor de API.
De HTML-pagina gebruikt een timer om elke vijf seconden een aanvraag naar de server te verzenden om aandelen aan te vragen. Er wordt een matrix van voorraden geretourneerd die vervolgens voor de gebruiker worden weergegeven.
<!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();
}
});
Zodra de startPoll
methode polling start, wordt de update
methode elke vijf seconden aangeroepen. Binnen de update
methode wordt een GET-aanvraag verzonden naar het /api/getStocks
API-eindpunt en wordt het resultaat ingesteld op app.stocks
, waarmee de gebruikersinterface wordt bijgewerkt.
De server- en clientcode is relatief eenvoudig: alle gegevens ophalen, alle gegevens weergeven. Zoals we in onze analyse ontdekken, brengt deze eenvoud enkele beperkingen met zich mee.
Analyse van prototypeoplossing
Als Tailwind Traders-engineer hebt u enkele nadelen van deze polling-benadering op basis van timers geïdentificeerd.
Onnodige API-aanvragen: in het prototype van de timergebaseerde polling neemt de clienttoepassing contact op met de server, ongeacht of er wijzigingen in de onderliggende gegevens bestaan.
Onnodige paginavernieuwing: zodra gegevens van de server worden geretourneerd, wordt de volledige lijst met aandelen bijgewerkt op de webpagina, zelfs als er geen gegevens zijn gewijzigd. Dit polling-mechanisme is een inefficiënte oplossing.
Polling-intervallen: het selecteren van het beste polling-interval voor uw scenario is ook een uitdaging. Door polling te gebruiken, moet u een keuze maken tussen de prijs van elke aanroep naar de back-end en de snelheid waarmee uw app op nieuwe gegevens moet reageren. Vertragingen bestaan vaak tussen de tijd waarop nieuwe gegevens beschikbaar komen en de tijd waarop de app deze detecteert. In de volgende afbeelding wordt het probleem weergegeven.
In het ergste geval is de mogelijke vertraging bij het detecteren van nieuwe gegevens hetzelfde als het polling-interval. Waarom is het dan niet handiger om een korter interval te gebruiken?
Hoeveelheid gegevens: naarmate de toepassing wordt geschaald, wordt de hoeveelheid gegevens die tussen de client en de server worden uitgewisseld een probleem. Elke HTTP-aanvraagheader bestaat uit honderden bytes aan gegevens en de sessiecookie. Door al deze overhead gaan resources verloren en wordt de server, met name onder zware druk, onnodig belast.
Nu u bekend bent met het prototype, is het tijd om de toepassing op uw computer uit te voeren.
CORS ondersteunen
In het local.settings.json-bestand van de Functions-app bevat de Host
sectie de volgende instellingen.
{
"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
}
}
Met deze configuratie kan een webtoepassing die wordt uitgevoerd op localhost:3000 aanvragen indienen bij de functie-app die wordt uitgevoerd op localhost:7071. De eigenschap CORSCredentials
geeft aan dat de functie-app referentiecookies van de aanvraag accepteert.
CORS (Cross-Origin Resource Sharing) is een HTTP-functie waarmee een webtoepassing die wordt uitgevoerd onder één domein, toegang kan krijgen tot resources in een ander domein. Webbrowsers implementeren een beveiligingsbeperking die bekend staat als same orgine-beleid waarmee wordt voorkomen dat een webpagina API's in een ander domein aanroept. CORS biedt een veilige manier om het ene domein (het oorspronkelijke domein) toe te staan om API's in een ander domein aan te roepen.
Wanneer cors lokaal wordt uitgevoerd, wordt CORS geconfigureerd in het local.settings.json-bestand van het voorbeeld, dat nooit wordt gepubliceerd. Wanneer u de client-app (les 7) implementeert, moet u ook de CORS-instellingen in de functie-app bijwerken om toegang vanuit de client-app toe te staan.