Tvorba internetových aplikací

Jakub Klinkovský

:: České vysoké učení technické v Praze
:: Fakulta jaderná a fyzikálně inženýrská
:: Katedra softwarového inženýrství

Akademický rok 2023-2024

Modely pro síťovou komunikaci

OSI model

OSI model vznikl v 1983 jako abstraktní základ pro standardizaci komunikačních protokolů.

  • je rozdělen do 7 vrstev, každá má své dobře definované funkce
  • nespecifikuje konkrétní sadu protokolů

Internet protocol suite (TCP/IP)

Internet protocol suite je sada komunikačních protokolů používaná v síti Internet.

  • má jen 4 vrstvy, je to praktická aplikace modelu OSI
  • název pochází ze dvou nejdůležitějších protokolů: TCP a IP

Srovnání modelů OSI a TCP/IP

center contain

Nejnižší vrstva umožňuje přístup k fyzickému přenosovému médiu. Je specifická pro každou síť v závislosti na její implementaci.

Příklady sítí: Ethernet, Wi-Fi, ADSL, PPP, ...

  • stará se o fyzický přenos dat
  • základní jednotka přenosu: rámec (v OSI modelu bity na fyzické vrstvě)

TCP/IP – síťová vrstva (internet layer)

Vrstva zajišťuje především síťovou adresaci, směrování a předávání datagramů.

  • nezávisí na konkrétní přenosové technologii (využívá vrstvu síťového rozhraní)
  • je implementována ve všech prvcích sítě (směrovače i koncová zařízení)
  • hlavní protokol: IP (IPv4, IPv6)
    • základní přenosová jednotka: IP datagram
    • adresní mechanismus: IP adresa (např. 192.168.0.10, fe80::862a:fdff:fe0f:fbd)
    • směrovací schéma pro přenos dat
  • další protokoly: ARP, RARP, ICMP, IGMP, IGRP, IPSEC, ...

TCP/IP – transportní vrstva (transport layer)

Poskytuje transportní služby pro dvojice aplikačních programů (koncových uzlů), tj. entity bezprostředně vyšší vrstvy.

  • je implementována až v koncových zařízeních (počítačích)
  • míra spolehlivosti přenosu závisí na zvoleném protokolu (odpovídá potřebám aplikace)
  • umožňuje přizpůsobit chování sítě potřebám aplikace

TCP/IP – nejběžnější protokoly transportní vrstvy

  • TCP (transmission control protocol)
    • spolehlivý transportní protokol (přeposílá ztracené packety, zajišťuje doručení ve správném pořadí)
    • z hlediska aplikace se přenos dat jeví jako spojitý proud
    • po navázání spojení je přenos plně duplexní (tok dat oběma směry)
  • UDP (user datagram protocol)
    • nespolehlivý protokol (neposkytuje kontrolu integrity)
    • poskytuje efektivní (rychlý) přenos dat na úkor spolehlivosti
    • na rozdíl od TCP nemá fázi navazování a ukončení spojení
  • TCP i UDP používají porty pro adresování dat jednotlivým aplikacím

TCP/IP – porty v protokolu TCP

Síťový port v protokolech TCP a UDP je číselné označení komunikačního bodu pro dané spojení.

  • příjemcem dat je proces, který je v danou chvíli k danému portu připojen
  • vztah mezi portem a aplikací je dynamický (procesy se dynamicky připojují a odpojují od jednotlivých portů)
  • nejběžnější čísla cílových portů: 22 (SSH), 53 (DNS), 80 (HTTP), 443 (HTTPS)

contain center

TCP/IP – aplikační vrstva (application layer)

Aplikační vrstva zahrnuje protokoly, které slouží k přenosu konkrétních dat.

Příklady:

  • DHCP – automatická konfigurace síťových zařízení (IP adresa, maska sítě, implicitní brána, DNS server, a další)
  • DNS – hierarchický, decentralizovaný systém doménových jmen
    • mapování symbolických jmen (domén) na IP adresy
    • např. jlk.fjfi.cvut.cz: doména nejvyšší úrovně cz, doména cvut, subdoména fjfi, atd.
  • HTTP – komunikace mezi webovými servery a klienty (viz dále)
  • Telnet, SSH, FTP, POP3, IMAP, SMTP, ...

Protokol HTTP

Komunikační protokol HTTP

HTTP (Hypertext Transfer Protocol) je protokol určený pro komunikaci s web servery. Slouží pro přenos hypertextových dokumentů ve formátech HTML, XML, a obecně libovolných souborů (např. multimédia, textové soubory).

  • používá obvykle port TCP/80
  • neumožňuje šifrování – zabezpečený protokol HTTPS (TLS spojení nad TCP/443)
  • architektura klient-server: klient vyšle požadavek (request) a od serveru obdrží odpověď (response)
  • je bezestavový – neudržuje informace o dřívějších spojeních a odeslaných datech (vzpomeňte si na REST)

Fungování protokolu HTTP

  1. navázání spojení
  2. odeslání požadavku z klienta na server
  3. odeslání odpovědi ze serveru klientovi
  4. ukončení spojení

Nejvíce času při načítání dokumentu zabere navázání spojení – zejména pro stránky s mnoha vloženými objekty. Proto vznikly různé optimalizace:

  • přenos více požadavků a odpovědí v jednom spojení, udržování trvalých spojení se serverem (keep-alive) – od verze HTTP/1.1
  • komprese hlaviček, přednostní zpracování požadavků – verze HTTP/2
  • optimalizace na úrovni transportní vrstvy s využitím UDP – verze HTTP/3

Struktura požadavku HTTP

method URL protocol
headers

message body

(Hlavičky a tělo požadavku jsou volitelné.)

Příklad:

GET /some_page/ HTTP/1.1
Host: localhost:8000
User-Agent: curl/7.88.1
Accept: */*

Struktura odpovědi HTTP

protocol status_code reason_phrase
headers

response body

(Hlavičky a tělo odpovědi jsou volitelné.)

Příklad:

HTTP/1.1 200 OK
Date: Sun, 12 Mar 2023 21:35:04 GMT
Server: WSGIServer/0.2 CPython/3.10.9
Content-Type: text/html; charset=utf-8

<!DOCTYPE html><html lang="en">...................................</html>

Metody požadavků HTTP

Metoda určuje typ požadavku, který klient od serveru požaduje. Klient může použít libovolnou definovanou metodu, daný server však nemusí podporovat všechny.

  • GET – nejčastější metoda, požadavek na dokument určený pomocí URL
  • HEAD – identická s metodou GET, ale server odešle jen hlavičky odpovědi
  • POST – odesílání dat z klienta na server pomocí těla požadavku, modifikace stavu serveru (např. uložení dat v databázi) – příklad
  • další: PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH

Stavové kódy HTTP

Stavové kódy určují obecný typ výsledku zpracování požadavku serverem. Jsou to tříciferné číselné kódy rozdělené do tematických skupin dle první číslice:

  • 1xx – informační – požadavek byl obdržen, zpracování pokračuje
  • 2xx – úspěch – požadavek byl obdržen, pochopen a akceptován
  • 3xx – přesměrování – klient musí provést další akce, aby dokončil požadavek
  • 4xx – chyba klienta – požadavek obsahuje neplatná data nebo nemůže být vyřízen
  • 5xx – chyba serveru – zpracování platného požadavku selhalo

Hlavičky HTTP

Hlavičky slouží k doplnění požadavku nebo odpovědi o metadata.

  • každá hlavička je dvojice klíč: hodnota
  • kódování hlaviček je buď textové (HTTP/1.1) nebo binární (HTTP/2 a HTTP/3)
  • příklad hlaviček požadavku:
    User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:110.0) Gecko/20100101 Firefox/110.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate, br
    
  • příklad hlaviček odpovědi:
    Content-Length: 767
    Content-Type: text/html; charset=utf-8
    Date: Mon, 13 Mar 2023 09:29:38 GMT
    Server: WSGIServer/0.2 CPython/3.10.9
    
  • seznam standardních hlaviček (lze definovat i vlastní klíče)

HTTP z pohledu klientských aplikací

Python – balíček requests

Requests: HTTP for Humans™ – viz 1. cvičení

  • obecná funkce requests.request(method, url, **kwargs)
  • "aliasy" pro jednotlivé metody HTTP:
    • requests.get(url, **kwargs)method == "GET"
    • requests.head(url, **kwargs)method == "HEAD"
    • requests.post(url, **kwargs)method == "POST"
    • ...
  • objektová reprezentace protokolu HTTP – výjimky, stavové kódy, transparentní kódování/dekódování dat v těle požadavku/odpovědi (např. JSON \leftrightarrow Python dict), hlavičky, autentizace a spousta pokročilých vlastností
  • umožňuje pouze blokující/synchronní zpracování požadavků

Python – balíček httpx

HTTPX: A next-generation HTTP client for Python

  • API je převážně kompatibilní s requests:
    • requests.get(...) \leftrightarrow httpx.get(...) apod.
  • poskytuje blokující i asynchronní rozhraní – klíčová slova async+await

Asynchronnní programování v Pythonu

Vysokoúrovňové API pro asynchronní operace vzniklo ve verzích Python 3.4 až 3.7:

  • dvě nová klíčová slova: async a await (definice tzv. coroutine)
  • standardní modul asyncio (API pro spouštění a správu coroutines)

Hello World příklad:

import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

asyncio.run(main())

Práce s coroutines

  1. asyncio.run() – top-level rozhraní pro spuštění async funkce ze synchronního kontextu
  2. awaiting on coroutines – použití klíčového slova await uvnitř async funkce
    • obecně existují 3 typy awaitable objektů:
      • coroutines – Python funkce označené jako async
      • Tasks – wrapper objekty sloužící k plánování coroutines, aby běžely současně
      • Futures – speciální nízkoúrovňové objekty, které představují výslednou hodnotu nějaké operace, která se dokončí v budoucnu
    • await umožňuje vyjádřit, které operace lze překrýt a zpracovávat souběžně

Příklad s několika coroutines

import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)

async def main():
    print(f"started at {time.strftime('%X')}")
    await say_after(1, 'hello')
    await say_after(2, 'world')
    print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

Možný výstup (všimněte si délky trvání):

started at 17:13:52
hello
world
finished at 17:13:55

Souběžné spouštění coroutines

asyncio.create_task() – naplánuje nějakou coroutine pro souběžné zpracování

Příklad:

async def main():
    task1 = asyncio.create_task( say_after(1, 'hello') )
    task2 = asyncio.create_task( say_after(2, 'world') )

    print(f"started at {time.strftime('%X')}")
    await task1
    await task2
    print(f"finished at {time.strftime('%X')}")

Všimněte si, že po této úpravě kód poběží o 1 sekundu rychleji (zpracování tasků je překryté při volání asyncio.sleep(delay) ve funkci say_after()).

Poznámka: Stále ale nejde o paralelní zpracování – Python využívá jen jedno vlákno.

Souběžné spouštění coroutines pomocí TaskGroup

Třída asyncio.TaskGroup – modernní alternativa k funkci asyncio.create_task()

Příklad:

async def main():
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task( say_after(1, 'hello') )
        task2 = tg.create_task( say_after(2, 'world') )

        print(f"started at {time.strftime('%X')}")

    # The await is implicit when the context manager exits.

    print(f"finished at {time.strftime('%X')}")

Synchronizace mezi tasky

API modulu asyncio poskytuje základní nástroje pro synchronizaci, podobně jako např. modul threading:

  • asyncio.Lock
  • asyncio.Event
  • asyncio.Condition
  • asyncio.Semaphore
  • asyncio.BoundedSemaphore
  • asyncio.Barrier

Event loop

  • asyncio.run() spustí nízkoúrovňovou event loop, kde dochází k plánování všech asynchronních tasků
  • samotné coroutines jsou k ničemu, dokud se nenavážou na event loop
  • defaultní event loop v interpretu CPython používá pouze jedno vlákno
  • event loops jsou pluggable – defaultní event loop lze nahradit jinou implementací
    • např. uvloop rychlá implementace v jazyku Cython (rychlost je srovnatelná s Go a dalšími staticky kompilovanými jazyky)
    • teoreticky je možné vyvinout i vícevláknovou event loop (ale pořád existuje omezení s GIL – globální zámek interpretu)

Využití asynchronního programování v aplikacích

Asynchronní API je užitečné zejména pro zakrytí latence při čekání na data (IO).

  • HTTP requesty – klientské a serverové balíčky (httpx, starlette)
  • databáze – sqlalchemy, ...
  • webové frameworky – Django, FastAPI, ...

Demo s balíčkem httpx: viz GitLab a zápisky

Asynchronní programování v JavaScriptu

JavaScript má také asynchronní funkce, podobně jako Python:

  • klíčová slova async a await s podobnou sémantikou
  • async funkci lze zavolat přímo – JavaScript nemá alternativu k asyncio.run()
  • async funkce vždy vrací objekt Promise, který má 3 možné stavy:
    pending, fulfilled, nebo rejected
  • await na objekt Promise buď vrátí výslednou hodnotu, nebo vyvolá výjimku
    • stejné chování jako v Pythonu z hlediska strukturování kódu
  • rozhraní pro souběžné zpracování: metody Promise.all, Promise.allSettled, Promise.any, Promise.race

Demo: Async Functions in JavaScript

Dokumentace: async function, Promise object

JavaScript Fetch API

Fetch API umožňuje v JavaScriptu zpracovávat HTTP požadavky asynchronním způsobem. Je to moderní alternativa k rozhraní XMLHttpRequest.

Základem je funkce fetch:

async function f() {
  try {
    const url = "https://example.com";
    await fetch(url, { mode: "cors" });
  } catch(err) {
    alert(err); // Failed to fetch
  }
}

Pozor: Prohlížeče z bezpečnostních důvodů blokují "unsafe" cross-origin requesty (CORS). Podrobnosti: Fetch: Cross-Origin Requests.

HTTP z pohledu serveru

Python web server pro statický web

Python obsahuje modul http.server, který lze použít přímo pro poskytování statického obsahu, nebo pro vývoj dynamických aplikací.

Přímé použití:

M:\>python -m http.server --help
usage: server.py [-h] [--cgi] [--bind ADDRESS] [--directory DIRECTORY] [port]

positional arguments:
  port                  specify alternate port (default: 8000)

options:
  -h, --help            show this help message and exit
  --cgi                 run as CGI server
  --bind ADDRESS, -b ADDRESS
                        specify alternate bind address (default: all interfaces)
  --directory DIRECTORY, -d DIRECTORY
                        specify alternate directory (default: current directory)

Např. python -m http.server --directory C:\Users\klinkjak\Documents

Python web server pro statický web – použití

  1. Otevřete terminál v adresáři, kde máte soubory pro INTA
    (případně použijte příkaz cd pro změnu adresáře)
  2. Najděte podadresář, který obsahuje aplikaci z minulého cvičení – např. cv03
    (zde by měly být soubory gallery.html apod.)
  3. Spusťte web server: python -m http.server --directory cv03
  4. V prohlížeči otevřete adresu web serveru: http://0.0.0.0:8000/gallery.html (nebo ekvivalentně http://localhost:8000/gallery.html)

Základní dynamický web server

Vytvoření základní dynamické aplikace pomocí modulu http.server je složitější než poskytování statického obsahu, ale stále stačí kód "na pár řádků". V následujícím příkladu si ukážeme, jak to funguje.

Demo: viz GitLab a zápisky


Poznámka: Používání modulu http.server pro vývoj kompletní webové aplikace je příliš komplikované a nepraktické. Pro tyto účely vznikly vysokoúrovňové frameworky, které výrazně usnadňují práci.

TODO: advanced CORS - https://javascript.info/fetch-crossorigin - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS