Analizowanie ograniczeń aplikacji internetowej opartej na sondowaniu
Bieżąca architektura aplikacji zgłasza informacje o zapasach, pobierając wszystkie informacje o cenach akcji z serwera na podstawie czasomierza. Taki projekt jest często określany mianem architektury opartej na sondowaniu.
Serwer
Informacje o cenach akcji są przechowywane w bazie danych usługi Azure Cosmos DB. Po wyzwoleniu przez żądanie HTTP funkcja getStocks
zwraca wszystkie wiersze z bazy danych.
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,
};
},
});
- Pobieranie danych: pierwsza sekcja kodu cosmosInput pobiera wszystkie elementy w
stocks
tabeli z zapytaniemSELECT * from c
wstocksdb
bazie danych w usłudze Cosmos DB. - Zwracane dane: druga sekcja kodu app.http odbiera te dane do funkcji jako dane wejściowe
context.extraInputs
, a następnie zwraca je jako treść odpowiedzi z powrotem do klienta.
Klient
Przykładowy klient używa Vue.js do tworzenia interfejsu użytkownika i klienta pobierania do obsługi żądań do interfejsu API.
Strona HTML używa czasomierza do wysyłania żądania do serwera co pięć sekund w celu żądania zasobów. Odpowiedź zwraca tablicę akcji, która następnie jest wyświetlana użytkownikowi.
<!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();
}
});
Po rozpoczęciu startPoll
sondowania metoda jest wywoływana update
co pięć sekund. update
Wewnątrz metody żądanie GET jest wysyłane do punktu końcowego interfejsu /api/getStocks
API, a wynik jest ustawiony na app.stocks
wartość , która aktualizuje interfejs użytkownika.
Kod serwera i klienta jest stosunkowo prosty: pobierz wszystkie dane, wyświetl wszystkie dane. Jak dowiemy się w naszej analizie, ta prostota wiąże się z pewnymi ograniczeniami.
Analiza prototypowego rozwiązania
Jako inżynier firmy Tailwind Traders zidentyfikowano niektóre wady tego podejścia do sondowania opartego na czasomierzu.
Niepotrzebne żądania interfejsu API: w prototypie sondowania opartego na czasomierzu aplikacja kliencka kontaktuje się z serwerem, czy istnieją zmiany w danych bazowych.
Niepotrzebne odświeżanie strony: po powrocie danych z serwera cała lista akcji jest aktualizowana na stronie internetowej, nawet jeśli żadne dane nie uległy zmianie. Taki mechanizm sondowania to nieefektywne rozwiązanie.
Interwały sondowania: wybór najlepszego interwału sondowania dla danego scenariusza jest również wyzwaniem. Sondowanie wymusza dokonanie wyboru między kosztem każdego wywołania zaplecza a szybkością reagowania aplikacji na nowe dane. Opóźnienia często występują między czasem, przez jaki nowe dane staną się dostępne, a czasem wykrycia przez aplikację. Poniższa ilustracja przedstawia ten problem.
W najgorszym przypadku potencjalne opóźnienie wykrywania nowych danych jest równe interwałowi sondowania. Dlaczego więc nie użyć mniejszego interwału?
Ilość danych: w miarę skalowania aplikacji ilość danych wymienianych między klientem a serwerem staje się problemem. Każdy nagłówek żądania HTTP to setki bajtów danych obejmujących plik cookie sesji. Cały ten narzut, szczególnie przy dużym obciążeniu, powoduje marnowanie zasobów i niepotrzebnie obciąża serwer.
Teraz, gdy znasz prototyp, nadszedł czas, aby aplikacja była uruchomiona na maszynie.
Obsługa mechanizmu CORS
W pliku local.settings.json aplikacji Host
usługi Functions sekcja zawiera następujące ustawienia.
{
"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
}
}
Ta konfiguracja umożliwia aplikacji internetowej działającej pod adresem localhost:3000 wykonywanie żądań do aplikacji funkcji uruchomionej pod adresem localhost:7071. Właściwość CORSCredentials
informuje aplikację funkcji o zaakceptowaniu plików cookie poświadczeń z żądania.
Mechanizm CORS (udostępnianie zasobów między źródłami) to funkcja protokołu HTTP, która umożliwia aplikacji internetowej działającej w ramach jednej domeny dostęp do zasobów w innej domenie. Przeglądarki internetowe wdrażają ograniczenie bezpieczeństwa nazywane zasadami jednego źródła, które zapobiega wywoływaniu interfejsów API w innych domenach przez strony internetowe. Mechanizm CORS zapewnia bezpieczną metodę umożliwiania jednej domenie (domenie źródłowej) wywoływania interfejsów API z innej domeny.
W przypadku uruchamiania lokalnego mechanizm CORS jest skonfigurowany dla Użytkownika w pliku local.settings.json przykładu, który nigdy nie jest publikowany. Podczas wdrażania aplikacji klienckiej (lekcja 7) należy również zaktualizować ustawienia mechanizmu CORS w aplikacji funkcji, aby zezwolić na dostęp z aplikacji klienckiej.