Connections: dlaczego jeden fetch() nie oznacza jednego połączenia
- data
- kategoria
- Frontend
- także w
- Networking
- czytanie
- 1 min / 276 słów
W kodzie:
await fetch("/api/a");
await fetch("/api/b");
widzimy dwa requesty.
Nie oznacza to:
two requests
=
two TCP connections
HTTP request i transport connection mają różny lifecycle.
Connection setup kosztuje
Dla klasycznego HTTPS over TCP ścieżka obejmuje co najmniej:
DNS
|
v
TCP connection
|
v
TLS
|
v
HTTP
Jeżeli każde fetch() tworzyłoby nową relację od zera, latency setupu dominowałoby przy małych requestach.
Dlatego browser reuse'uje connections.
HTTP/1.1 persistent connections
W nowoczesnym HTTP/1.1 połączenie jest domyślnie persistent, chyba że strony uzgodnią jego zamknięcie.
Czyli:
TCP connection
├── request A -> response A
├── request B -> response B
└── request C -> response C
To usuwa koszt zestawiania TCP/TLS dla każdego requestu.
Ale HTTP/1.1 ma ograniczenie: współbieżność na jednym połączeniu jest problematyczna.
To doprowadziło browsery do utrzymywania kilku connections do originu.
fetch() nie kontroluje socket poola
Frontend nie robi:
fetch(url, {
tcpConnection: 3
});
Browser zarządza connection pool.
To on decyduje:
- czy reuse'ować istniejące connection,
- czy zestawić nowe,
- jaki protokół został wynegocjowany,
- czy request może współdzielić connection z innymi requestami.
Frontend wpływa na to pośrednio przez architekturę zasobów i originów.
Origin ma znaczenie
Requesty do:
https://app.example.com
https://api.example.com
https://cdn.example.com
nie są automatycznie jednym logicznym connection poolem tylko dlatego, że należą do tej samej firmy.
Historycznie frontend próbował zwiększać równoległość przez rozbijanie assetów na wiele hostname'ów:
img1.example.com
img2.example.com
img3.example.com
To nazywano domain sharding.
Było odpowiedzią na ograniczenia HTTP/1.1.
W HTTP/2 często staje się kontrproduktywne, bo rozbija ruch, który mógłby korzystać ze wspólnego multiplexed connection.
Keep-alive w fetch() to inny temat
Fetch ma opcję:
fetch("/analytics", {
method: "POST",
body: data,
keepalive: true
});
Nazwa może mylić.
To nie jest przełącznik HTTP persistent connection.
Fetch keepalive dotyczy możliwości kontynuowania określonego requestu po rozpoczęciu unload strony, np. dla małej telemetrii.
Nie należy mieszać:
Fetch keepalive option
z:
HTTP connection persistence
Frontendowy problem HTTP/1.1
Mamy więc persistent connections, ale pojedyncze połączenie nie daje nam jeszcze wygodnej współbieżności wielu niezależnych response'ów.
A typowa strona potrzebuje:
HTML
CSS
JS
fonts
images
API calls
To właśnie ograniczenia tego modelu stworzyły charakterystyczny network waterfall HTTP/1.1.
I przez lata wpływały na sposób, w jaki budowaliśmy frontend bundles.