Asynkroninen ohjelmointi on monessa tapauksessa tehokas työkalu sovellusten suorituskyvyn parantamiseen, erityisesti silloin, kun käsitellään korkean latenssin operaatioita, kuten tietokanta- tai verkko-osoitepyyntöjä. Tällöin tärkeimmät edut tulevat esiin siinä, että pääsäie ei esty odottamaan operaatioiden valmistumista, vaan sovellus voi jatkaa muiden tehtävien käsittelyä samalla kun odottaa vastausta.

Yksi esimerkki asynkronisten toimintojen hyödyntämisestä on verkko-osoitteiden kutsuminen. Tähän voidaan käyttää esimerkiksi FastAPI:n ja asyncio-kirjaston tarjoamia mahdollisuuksia. Otetaanpa tarkasteluun esimerkki, jossa tehdään samanaikaisia pyyntöjä kahteen eri päätepisteeseen – synkroniseen ja asynkroniseen.

Ensimmäinen askel on määritellä palvelimen käynnistäminen erillisessä prosessissa käyttäen context manageria. Tämä voi näyttää seuraavalta:

python
from contextlib import contextmanager from multiprocessing import Process @contextmanager def run_server_in_process(): p = Process(target=run_server) p.start() time.sleep(2) # Anna palvelimelle hetki käynnistyä print("Palvelin käynnistyy erillisessä prosessissa") yield p.terminate()

Tässä käytämme contextmanager-koristetta luodaksemme yksinkertaisen kontekstin, joka käynnistää palvelimen ja sulkee sen oikein käytön jälkeen. Seuraavaksi voimme luoda asynkronisen funktion, joka suorittaa n samanaikaista pyyntöä tietylle polulle.

python
async def make_requests_to_the_endpoint(n: int, path: str):
async with AsyncClient(base_url="http://localhost:8000") as client:
tasks = ( client.get(path, timeout=
float("inf")) for _ in range(n) ) await asyncio.gather(*tasks)

Tässä AsyncClient suorittaa HTTP GET -pyynnöt, ja asyncio.gather odottaa, että kaikki pyynnöt ovat valmiit ennen kuin etenee seuraavaan vaiheeseen. Tämä voi merkittävästi parantaa suorituskykyä verrattuna synkroniseen lähestymistapaan.

Kun nämä osat on rakennettu, voidaan tehdä pääfunktio, joka kutsuu näitä funktioita ja mittaa, kuinka kauan pyynnöt kestävät. Esimerkiksi:

python
async def main(n: int = 10): with run_server_in_process(): begin = time.time() await make_requests_to_the_endpoint(n, "/sync") end = time.time()
print(f"Aika {n} synkroniselle pyynnölle: {end - begin} sekuntia")
begin = time.time()
await make_requests_to_the_endpoint(n, "/async") end = time.time() print(f"Aika {n} asynkroniselle pyynnölle: {end - begin} sekuntia")

Tässä mittaamme aikaa, joka kuluu n pyynnön tekemiseen synkronisesti ja asynkronisesti. Jos pyynnön määrä on suuri, asynkroninen malli tuo merkittävän parannuksen suoritusaikaan.

Testin tulokset voivat näyttää tältä:

nginx
Aika 10 synkroniselle pyynnölle: 2.317 sekuntia
Aika 10 asynkroniselle pyynnölle: 2.303 sekuntia

Vaikka 10 pyynnön välillä ei ole merkittävää eroa, suuremmilla määrillä, kuten 100 pyynnöllä, asynkroninen lähestymistapa tuo huomattavan edun:

nginx
Aika 100 synkroniselle pyynnölle: 6.425 sekuntia Aika 100 asynkroniselle pyynnölle: 2.423 sekuntia

Tämä parannus johtuu asynkronisten funktioiden käytöstä, joka mahdollistaa sen, että pääsäie ei jää odottamaan yksittäisten pyyntöjen valmistumista.

Asynkronisten operaatioiden hyödyntäminen ei kuitenkaan ole aina oikea valinta. Asynkronia tuo monia etuja erityisesti I/O-keskeisissä operaatioissa, kuten verkkopyynnöissä tai tietokantaoperaatioissa, mutta se voi lisätä monimutkaisuutta ja virheiden hallintaa, erityisesti silloin, kun tarvitaan useita samanaikaisia tehtäviä.

Tässä on joitain parhaita käytäntöjä asynkronisen ohjelmoinnin hyödyntämisessä FastAPI:ssa:

  • I/O-keskeiset toiminnot: Asynkroninen ohjelmointi on erityisen hyödyllistä I/O-keskeisissä toiminnoissa, kuten tietokantaoperaatioissa, verkkopyynnöissä ja tiedosto-operaatioissa. Sen sijaan raskaat laskentatehtävät, jotka kuluttavat paljon prosessoritehoa, eivät hyödy asynkroniasta yhtä paljon.

  • Tietokantatransaktiot: Asynkronisten tietokantaoperaatioiden yhteydessä on tärkeää hallita transaktioita huolellisesti. Tämä varmistaa, että tiedon eheys säilyy. Yksi tapa tehdä tämä on käyttää async with-kontekstinhallintaa.

  • Virheiden käsittely: Asynkroninen ohjelmointi voi tehdä virheiden käsittelystä monimutkaisempaa, erityisesti silloin, kun useita samanaikaisia tehtäviä suoritetaan. On suositeltavaa käyttää try-except-lohkot virheiden asianmukaiseen käsittelyyn.

  • Testaus: Asynkronisten toimintojen testaaminen vaatii erityistä huomiota. Varmista, että testikehys tukee asynkronisia testejä ja käytä async ja await-avainsanoja tarpeen mukaan testikoodissasi.

Näiden periaatteiden ymmärtäminen ja soveltaminen auttaa rakentamaan sovelluksia, jotka eivät ole vain kestäviä, vaan myös suorituskykyisiä ja optimoituja kuormitustilanteissa.

Miten WebSocket-yhteyksien hallinta toteutetaan FastAPI:ssa chat-sovelluksessa?

WebSocket-yhteyksien hallinta on keskeinen osa reaaliaikaisia chat-sovelluksia, ja FastAPI tarjoaa tähän tehokkaat asynkroniset työkalut. Yhteyksien hallinta voidaan toteuttaa luomalla erillinen ConnectionManager-luokka, jonka vastuulla on ylläpitää aktiivisten WebSocket-yhteyksien listaa, vastaanottaa ja lähettää viestejä sekä käsitellä yhteyksien muodostaminen ja katkaiseminen.

ConnectionManager-luokka sisältää menetelmät, jotka ovat keskeisiä WebSocket-toiminnallisuudelle. connect-metodi lisää uuden yhteyden aktiivisten yhteyksien joukkoon ja vastaa kädenpuristuksesta (handshake). disconnect-metodi puolestaan poistaa WebSocket-yhteyden listalta, kun asiakas irrottautuu. Viestien lähetys voidaan jakaa kahteen päätyyppiin: yksittäiselle yhteydelle kohdennettu viesti (send_personal_message) ja viestin välittäminen kaikille muille aktiivisille yhteyksille paitsi lähettäjälle (broadcast). Viestien lähetys toteutetaan asynkronisesti, hyödyntäen asyncio.gather-funktiota, joka mahdollistaa rinnakkaisen suorittamisen.

WebSocket-päätepiste määritellään erillisessä moduulissa, esimerkiksi chat.py:ssä. Tämä parantaa sovelluksen rakennetta ja ylläpidettävyyttä. Päätepisteessä, joka on merkitty reitille /chatroom/{username}, kutsutaan yhteyksien hallintaluokan metodeja yhteyden muodostamiseksi, viestien vastaanottamiseksi ja niiden broadcastaamiseksi. Kun uusi käyttäjä liittyy chattiin, lähetetään ilmoitus muille osallistujille. Viestien vastaanotto tapahtuu silmukassa, joka kuuntelee jatkuvasti uusia viestejä WebSocketin kautta. Jokainen viesti lähetetään kaikille muille, ja lähettäjälle lähetetään vahvistusviesti henkilökohtaisesti.

Kun yhteys katkeaa, esimerkiksi käyttäjän poistuessa, WebSocketDisconnect-poikkeus käsitellään irrottamalla yhteys ja lähettämällä ilmoitus muille. Tämä varmistaa reaaliaikaisen tilannetietoisuuden kaikille käyttäjille.

FastAPI:n reititin liitetään pääsovellukseen include_router-metodilla, jolloin WebSocket-päätepisteet ovat käytettävissä sovelluksen kautta. Lisäksi chat-sovelluksen käyttöliittymä voidaan toteuttaa yksinkertaisena HTML-sivuna, joka renderöidään Jinja2-mallinnuksella. Sivulle lisätään JavaScript-koodi, joka avaa WebSocket-yhteyden ja mahdollistaa viestien lähettämisen ja vastaanoton selaimessa.

On tärkeää ymmärtää, että WebSocket-pohjainen chat-ratkaisu perustuu vahvasti asynkroniseen ohjelmointiin ja yhteyksien hallintaan rinnakkaisesti. Suorituskyvyn ja skaalautuvuuden kannalta on olennaista, että kaikki yhteydet ja viestinvälitykset hoidetaan ei-estävällä tavalla. Tämä mahdollistaa suuren käyttäjämäärän käsittelyn ilman, että yksittäinen hidas yhteys hidastaa koko järjestelmää.

Lisäksi on merkittävää huomioida, että WebSocket-yhteyksien hallinnassa tulee varautua myös poikkeustilanteisiin, kuten yhteyden odottamattomaan katkeamiseen tai virheellisiin viesteihin, jotta sovellus pysyy vakaana ja turvallisena. Käyttäjien tunnistaminen, esimerkiksi polun parametrina välitettävän käyttäjänimen avulla, on myös olennainen osa chatin toimivuutta ja käyttäjäkokemusta.

Miten kehittää väliohjelmistoa vastauksen muokkaamiseksi FastAPI:ssa?

Väliohjelmisto on ohjelmiston kerros, joka sijaitsee sovelluksen ja HTTP-pyynnön käsittelijän välillä. Se tarjoaa joustavan tavan käsitellä pyyntöjä ja vastauksia ennen kuin ne saavuttavat loppukäyttäjän tai ennen kuin pyyntö menee edelleen palvelimelle. Tällaisen välikerroksen hyödyntäminen voi tuoda monia etuja, kuten turvallisuuden parantamisen, suorituskyvyn optimoinnin tai erityisten otsikoiden lisäämisen vastauksiin ilman, että tarvitsee muokata alkuperäisiä käsittelijöitä.

Tässä artikkelissa tarkastelemme, kuinka luodaan väliohjelmisto FastAPI:lle, joka muokkaa HTTP-vastauksia lisäämällä niihin uusia otsikoita. Tämä voidaan toteuttaa Starlette-kirjaston avulla, joka tarjoaa kaikki tarvittavat työkalut FastAPI:n väliohjelmistojen luomiseen ja käsittelemiseen.

Ensimmäinen askel on luoda ExtraHeadersResponseMiddleware -luokka, joka ottaa vastaan listan lisättävistä otsikoista ja liittää ne jokaiseen vastaukseen ennen sen lähettämistä asiakkaalle. Tämä luokka tarvitsee muutaman keskeisen komponentin: app (FastAPI-sovelluksen instanssi), headers (otsikoiden lista) sekä __call__-metodi, joka käsittelee pyynnöt ja vastaukset.

python
class ExtraHeadersResponseMiddleware: def __init__(self, app: ASGIApp, headers: Sequence[tuple[str, str]]): self.app = app self.headers = headers
async def __call__(self, scope: Scope, receive: Receive, send: Send):
if scope["type"] != "http": return await self.app(scope, receive, send) async def send_with_extra_headers(message: Message): if message["type"] == "http.response.start": headers = MutableHeaders(scope=message) for key, value in self.headers: headers.append(key, value) await send(message) await self.app(scope, receive, send_with_extra_headers)

Tässä määritellään ExtraHeadersResponseMiddleware-luokka, joka tarkistaa, onko saapuva pyyntö HTTP-tyyppinen. Mikäli pyyntö ei ole HTTP-tyyppinen, se ohjataan normaalisti seuraavaan vaiheeseen. Muussa tapauksessa väliohjelmisto lisää määritellyt otsikot send-funktion kautta vastaukseen, joka lähetetään asiakkaalle.

Kun tämä väliohjelmisto on luotu, se tulee lisätä FastAPI-sovellukseen. Tämä voidaan tehdä app.add_middleware-metodilla, jossa määritellään lisättävät otsikot. Esimerkiksi:

python
app.add_middleware( ExtraHeadersResponseMiddleware, headers=( ("new-header", "fastapi-cookbook"), ("another-header", "fastapi-cookbook"), ), )

Kun sovellus on käynnissä, voidaan tarkistaa, että otsikot on lisätty onnistuneesti, tarkastelemalla vastauksen otsikoita selaimessa tai API-työkalussa, kuten Postmanissa tai curl-komennolla.

Toinen tärkeä näkökohta on CORS (Cross-Origin Resource Sharing) -politiikka. CORS on tärkeä turvallisuusominaisuus, joka estää kolmansien osapuolten verkkosivustoja tekemästä luvattomia pyyntöjä toisille verkkosivustoille. Tämä estää niin sanotut XSS-hyökkäykset ja muut verkkohyökkäykset, jotka voivat käyttää verkkosovelluksia väärin. CORS-politiikan hallinta voidaan toteuttaa erillisen CORSMiddleware-luokan avulla, joka antaa sovelluksen määritellä, mitkä alkuperät voivat tehdä pyyntöjä sovellukseen.

python
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], )

Tässä määritellään, että kaikki alkuperät, HTTP-menetelmät ja otsikot ovat sallittuja. Tämä on käytännöllistä kehitysympäristössä, mutta tuotantoympäristössä on tärkeää rajata nämä asetukset tiukemmiksi, jotta sovellus pysyy turvassa. CORS-politiikan hallinta mahdollistaa sovelluksen joustavan käytön erilaisissa verkkoympäristöissä samalla, kun se estää haitallisia pyyntöjä.

Kolmas tärkeä osa sovellusten turvallisuuden parantamista on varmistaa, että vain tietyt luotetut isäntäkoneet voivat tehdä pyyntöjä palvelimelle. Tämän voi toteuttaa TrustedHostMiddleware-luokalla, joka varmistaa, että pyyntö hyväksytään vain määritellyistä isännistä. Tämä on erityisen tärkeää estämään DNS-rebinding-hyökkäyksiä, joissa haitallinen toimija voi huijata käyttäjän selaimen tekemään pyyntöjä väärille palvelimille.

python
from fastapi.middleware.trustedhost import TrustedHostMiddleware
app.add_middleware( TrustedHostMiddleware, allowed_hosts=["localhost"] )

Tässä esimerkissä pyyntö hyväksytään vain, jos se tulee localhost-isännältä. Tämä auttaa estämään epäluotettavien lähteiden pääsyn sovellukseen.

Yhteenvetona voidaan todeta, että FastAPI ja Starlette tarjoavat monia tehokkaita työkaluja väliohjelmistojen kehittämiseen ja hallintaan. Kehittäjät voivat helposti lisätä erikoistoimintoja, kuten otsikoiden muokkaamista, CORS-hallintaa ja isäntärajoituksia, parantaakseen sovelluksensa turvallisuutta ja joustavuutta. On kuitenkin tärkeää, että jokaisessa käytetyssä väliohjelmistossa noudatetaan hyviä turvallisuuskäytäntöjä, erityisesti tuotantoympäristössä.